• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

benrr101 / node-taglib-sharp / 48257345

12 Oct 2023 04:06AM UTC coverage: 92.315% (+0.4%) from 91.944%
48257345

push

appveyor

benrr101
Performer roles (bug fixed), genres

3226 of 4153 branches covered (0.0%)

Branch coverage included in aggregate %.

94 of 94 new or added lines in 2 files covered. (100.0%)

27069 of 28664 relevant lines covered (94.44%)

417.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

31.85
/src/mpeg4/mpeg4File.ts
1
import AppleTag from "./appleTag";
1✔
2
import IsoChunkLargeOffsetBox from "./boxes/isoChunkLargeOffsetBox";
1✔
3
import IsoChunkOffsetBox from "./boxes/isoChunkOffsetBox";
1✔
4
import IsoUserDataBox from "./boxes/isoUserDataBox";
1✔
5
import Mpeg4BoxRenderer from "./mpeg4BoxRenderer";
1✔
6
import Mpeg4BoxType from "./mpeg4BoxType";
1✔
7
import Mpeg4FileParser from "./mpeg4FileParser";
1✔
8
import { File, FileAccessMode, ReadStyle } from "../file";
1✔
9
import { IFileAbstraction } from "../fileAbstraction";
10
import { Properties } from "../properties";
1✔
11
import { Tag, TagTypes } from "../tag";
1✔
12
import { NumberUtils } from "../utils";
1✔
13

14
/**
15
 * Provides tagging and properties support for Mpeg4 files.
16
 */
17
export default class Mpeg4File extends File {
1✔
18
    /**
19
     * Contains the Apple tag.
20
     */
21
    private _tag: AppleTag;
22

23
    /**
24
     * Contains the media properties.
25
     */
26
    private _properties: Properties;
27

28
    /**
29
     * Contains the UDTA Boxes
30
     */
31
    private readonly _udtaBoxes: IsoUserDataBox[] = [];
30✔
32

33
    /** @inheritDoc */
34
    public constructor(file: IFileAbstraction | string, readStyle: ReadStyle) {
35
        super(file);
30✔
36

37
        this.read(readStyle);
30✔
38
    }
39

40
    /** @inheritDoc */
41
    public get tag(): AppleTag {
42
        return this._tag;
283✔
43
    }
44

45
    /** @inheritDoc */
46
    public get properties(): Properties {
47
        return this._properties;
×
48
    }
49

50
    protected get udtaBoxes(): IsoUserDataBox[] {
51
        return this._udtaBoxes;
60✔
52
    }
53

54
    /** @inheritDoc */
55
    public getTag(types: TagTypes, create: boolean): Tag {
56
        if (types === TagTypes.Apple) {
×
57
            if (!this._tag && create) {
×
58
                let udtaBox = this.findAppleTagUdta();
×
59
                if (!udtaBox) {
×
60
                    udtaBox = IsoUserDataBox.fromEmpty();
×
61
                }
62

63
                this._tag = new AppleTag(udtaBox);
×
64
            }
65

66
            return this._tag;
×
67
        }
68

69
        return undefined;
×
70
    }
71

72
    /** @inheritDoc */
73
    public removeTags(types: TagTypes): void {
74
        if ((types & TagTypes.Apple) !== TagTypes.Apple || !this._tag) {
×
75
            return;
×
76
        }
77

78
        this._tag.detachIlst();
×
79
        this._tag = undefined;
×
80
    }
81

82
    /** @inheritDoc */
83
    public save(): void {
84
        // Boilerplate
85
        this.preSave();
×
86

87
        if (this.udtaBoxes.length === 0) {
×
88
            const udtaBox = IsoUserDataBox.fromEmpty();
×
89
            this.udtaBoxes.push(udtaBox);
×
90
        }
91

92
        // Try to get into write mode.
93
        this.mode = FileAccessMode.Write;
×
94

95
        try {
×
96
            const parser = new Mpeg4FileParser(this);
×
97
            parser.parseBoxHeaders();
×
98

99
            let sizeChange: number;
100
            let writePosition: number;
101

102
            // To avoid rewriting udta blocks which might not have been modified,
103
            // the code here will work correctly if:
104
            // 1. There is a single udta for the entire file
105
            //   - OR -
106
            // 2. There are multiple utdtas, but only 1 of them contains the Apple ILST box.
107
            // We should be OK in the vast majority of cases
108

109
            let udtaBox = this.findAppleTagUdta();
×
110

111
            if (!udtaBox) {
×
112
                udtaBox = IsoUserDataBox.fromEmpty();
×
113
            }
114

115
            const tagData = Mpeg4BoxRenderer.renderBox(udtaBox);
×
116

117
            // If we don't have a "udta" box to overwrite...
118
            if (!udtaBox.parentTree || udtaBox.parentTree.length === 0) {
×
119
                // Stick the box at the end of the moov box.
120
                const moovHeader = parser.moovTree[parser.moovTree.length - 1];
×
121
                sizeChange = tagData.length;
×
122
                writePosition = moovHeader.position + moovHeader.totalBoxSize;
×
123
                this.insert(tagData, writePosition, 0);
×
124

125
                // Overwrite the parent box sizes.
126
                for (let i = parser.moovTree.length - 1; i >= 0; i--) {
×
127
                    sizeChange = parser.moovTree[i].overwrite(this, sizeChange);
×
128
                }
129
            } else {
130
                // Overwrite the old box.
131
                const udtaHeader = udtaBox.parentTree[udtaBox.parentTree.length - 1];
×
132
                sizeChange = tagData.length - udtaHeader.totalBoxSize;
×
133
                writePosition = udtaHeader.position;
×
134
                this.insert(tagData, writePosition, udtaHeader.totalBoxSize);
×
135

136
                // Overwrite the parent box sizes.
137
                for (let i = udtaBox.parentTree.length - 2; i >= 0; i--) {
×
138
                    sizeChange = udtaBox.parentTree[i].overwrite(this, sizeChange);
×
139
                }
140
            }
141

142
            // If we've had a size change, we may need to adjust chunk offsets.
143
            if (sizeChange !== 0) {
×
144
                // We may have moved the offset boxes, so we need to reread.
145
                parser.parseChunkOffsets();
×
146

147
                for (const box of parser.chunkOffsetBoxes) {
×
148
                    if (box instanceof IsoChunkLargeOffsetBox) {
×
149
                        (<IsoChunkLargeOffsetBox>box).updatePosition(sizeChange, writePosition);
×
150

151
                        const updatedBox = Mpeg4BoxRenderer.renderBox(box);
×
152
                        this.insert(updatedBox, box.header.position, box.size);
×
153
                    } else if (box instanceof IsoChunkOffsetBox) {
×
154
                        (<IsoChunkOffsetBox>box).updatePositions(sizeChange, writePosition);
×
155

156
                        const updatedBox = Mpeg4BoxRenderer.renderBox(box);
×
157
                        this.insert(updatedBox, box.header.position, box.size);
×
158
                    }
159
                }
160
            }
161

162
            this.tagTypesOnDisk = this.tagTypes;
×
163
        } finally {
164
            this.mode = FileAccessMode.Closed;
×
165
        }
166
    }
167

168
    /**
169
     * Find the udta box within our collection that contains the Apple ILST data.
170
     * @returns The udta box within our collection that contains the Apple ILST data.
171
     */
172
    private findAppleTagUdta(): IsoUserDataBox {
173
        if (this.udtaBoxes.length === 1) {
30!
174
            return this.udtaBoxes[0]; // Single udta - just return it
30✔
175
        }
176

177
        // Multiple udta: pick out the shallowest node which has an ILst tag
178
        const possibleUdtaBoxes = this.udtaBoxes
×
179
            .filter((box) => box.getChildRecursively(Mpeg4BoxType.ILST))
×
180
            .sort((box1, box2) => (box1.parentTree.length < box2.parentTree.length ? -1 : 1));
×
181

182
        if (possibleUdtaBoxes.length > 0) {
×
183
            return possibleUdtaBoxes[0];
×
184
        }
185

186
        return undefined;
×
187
    }
188

189
    /**
190
     * Gets if there is a udta with ILST present in our collection
191
     * @returns True if there is a udta with ILST present in our collection
192
     */
193
    private isAppleTagUdtaPresent(): boolean {
194
        for (const udtaBox of this._udtaBoxes) {
30✔
195
            if (udtaBox.getChild(Mpeg4BoxType.META)?.getChild(Mpeg4BoxType.ILST)) {
30!
196
                return true;
×
197
            }
198
        }
199
        return false;
30✔
200
    }
201

202
    private read(readStyle: ReadStyle): void {
203
        this.mode = FileAccessMode.Read;
30✔
204

205
        try {
30✔
206
            // Read the file
207
            const parser = new Mpeg4FileParser(this);
30✔
208

209
            if ((readStyle & ReadStyle.Average) === 0) {
30!
210
                parser.parseTag();
30✔
211
            } else {
212
                parser.parseTagAndProperties();
×
213
            }
214

215
            this._udtaBoxes.push(...parser.userDataBoxes);
30✔
216

217
            // Ensure our collection contains at least a single empty box
218
            if (this._udtaBoxes.length === 0) {
30!
219
                const dummy = IsoUserDataBox.fromEmpty();
30✔
220
                this._udtaBoxes.push(dummy);
30✔
221
            }
222

223
            // Check if a udta with ILST actually exists
224
            if (this.isAppleTagUdtaPresent()) {
30!
225
                // There is an udta present with ILST info
226
                this.tagTypesOnDisk = NumberUtils.uintOr(this.tagTypesOnDisk, TagTypes.Apple);
×
227
            }
228

229
            // Find the udta box with the Apple Tag ILST
230
            let udtaBox = this.findAppleTagUdta();
30✔
231
            if (!udtaBox) {
30!
232
                udtaBox = IsoUserDataBox.fromEmpty();
×
233
            }
234

235
            this._tag = new AppleTag(udtaBox);
30✔
236

237
            // If we're not reading properties, we're done.
238
            if ((readStyle & ReadStyle.Average) === 0) {
30!
239
                return;
30✔
240
            }
241

242
            // Get the movie header box.
243
            const mvhdBox = parser.movieHeaderBox;
×
244

245
            if (!mvhdBox) {
×
246
                throw new Error("mvhd box not found.");
×
247
            }
248

249
            const audioSampleEntry = parser.audioSampleEntry;
×
250
            const visualSampleEntry = parser.visualSampleEntry;
×
251

252
            // Read the properties.
253
            this._properties = new Properties(mvhdBox.durationInMilliseconds, [audioSampleEntry, visualSampleEntry]);
×
254
        } finally {
255
            this.mode = FileAccessMode.Closed;
30✔
256
        }
257
    }
258
}
259

260
// /////////////////////////////////////////////////////////////////////////
261
// Register the file type
262
[
1✔
263
    "taglib/m4a",
264
    "taglib/m4b",
265
    "taglib/m4v",
266
    "taglib/m4p",
267
    "taglib/mp4",
268
    "audio/mp4",
269
    "audio/x-m4a",
270
    "video/mp4",
271
    "video/x-m4v"
272
].forEach(
273
    (mt) => File.addFileType(mt, Mpeg4File)
9✔
274
);
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc