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

benrr101 / node-taglib-sharp / 47556045

pending completion
47556045

push

appveyor

Ben Russell
fixing-missing-settings

3255 of 4223 branches covered (77.08%)

Branch coverage included in aggregate %.

26553 of 28216 relevant lines covered (94.11%)

412.85 hits per line

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

18.93
/src/mpeg4/mpeg4FileParser.ts
1
import { ByteVector, StringType } from "../byteVector";
1✔
2
import { File } from "../file";
3
import { Guards } from "../utils";
1✔
4
import { IsoAudioSampleEntry, IsoHandlerBox, IsoMovieHeaderBox, IsoUserDataBox, IsoVisualSampleEntry, Mpeg4Box } from "./mpeg4Boxes";
1✔
5
import Mpeg4BoxFactory from "./mpeg4BoxFactory";
1✔
6
import Mpeg4BoxHeader from "./mpeg4BoxHeader";
1✔
7
import Mpeg4BoxType from "./mpeg4BoxType";
1✔
8
import Mpeg4Utils from "./mpeg4Utils";
1✔
9

10
/**
11
 * This class provides methods for reading important information from an MPEG-4 file.
12
 */
13
export default class Mpeg4FileParser {
1✔
14
    /**
15
     * Contains the file to read from.
16
     */
17
    private readonly _file: File;
18

19
    /**
20
     * Contains the first header found in the file.
21
     */
22
    private readonly _firstHeader: Mpeg4BoxHeader;
23

24
    /**
25
     * Contains the ISO movie header box.
26
     */
27
    private _mvhdBox: IsoMovieHeaderBox;
28

29
    /**
30
     * Contains the ISO user data boxes.
31
     */
32
    private _udtaBoxes: IsoUserDataBox[] = [];
30✔
33

34
    /**
35
     * Contains the box headers from the top of the file to the "moov" box.
36
     */
37
    private _moovTree: Mpeg4BoxHeader[];
38

39
    /**
40
     * Contains the box headers from the top of the file to the "udta" box.
41
     */
42
    private _udtaTree: Mpeg4BoxHeader[];
43

44
    /**
45
     * Contains the "stco" boxes found in the file.
46
     */
47
    private _stcoBoxes: Mpeg4Box[] = [];
30✔
48

49
    /**
50
     * Contains the "stsd" boxes found in the file.
51
     */
52
    private _stsdBoxes: Mpeg4Box[] = [];
30✔
53

54
    /**
55
     * Contains the position at which the "mdat" box starts.
56
     */
57
    private _mdatStart: number = -1;
30✔
58

59
    /**
60
     * Contains the position at which the "mdat" box ends.
61
     */
62
    private _mdatEnd: number = -1;
30✔
63

64
    /**
65
     * Constructs and initializes a new instance of @see FileParser for a specified file.
66
     * @param file A @see File object to perform operations on.
67
     */
68
    public constructor(file: File) {
69
        Guards.notNullOrUndefined(file, "File");
30✔
70

71
        this._file = file;
30✔
72
        this._firstHeader = Mpeg4BoxHeader.fromFileAndPosition(file, 0);
30✔
73

74
        if (this._firstHeader.boxType.toString(StringType.UTF8) !== "ftyp") {
30!
75
            throw new Error("File does not start with 'ftyp' box.");
×
76
        }
77
    }
78

79
    /**
80
     * Gets the movie header box read by the current instance.
81
     */
82
    public get movieHeaderBox(): IsoMovieHeaderBox {
83
        return this._mvhdBox;
×
84
    }
85

86
    /**
87
     * Gets all user data boxes read by the current instance.
88
     */
89
    public get userDataBoxes(): IsoUserDataBox[] {
90
        return this._udtaBoxes;
30✔
91
    }
92

93
    /**
94
     * Gets the audio sample entry read by the current instance.
95
     */
96
    public get audioSampleEntry(): IsoAudioSampleEntry {
97
        for (const box of this._stsdBoxes) {
×
98
            for (const sub of box.children) {
×
99
                if (sub instanceof IsoAudioSampleEntry) {
×
100
                    return sub;
×
101
                }
102
            }
103
        }
104

105
        return undefined;
×
106
    }
107

108
    /**
109
     * Gets the visual sample entry read by the current instance.
110
     */
111
    public get visualSampleEntry(): IsoVisualSampleEntry {
112
        for (const box of this._stsdBoxes) {
×
113
            for (const sub of box.children) {
×
114
                if (sub instanceof IsoVisualSampleEntry) {
×
115
                    return sub;
×
116
                }
117
            }
118
        }
119

120
        return undefined;
×
121
    }
122

123
    /**
124
     * Gets the box headers for the first "moov" box and
125
     * all parent boxes up to the top of the file as read by the
126
     * current instance.
127
     */
128
    public get moovTree(): Mpeg4BoxHeader[] {
129
        return this._moovTree;
×
130
    }
131

132
    /**
133
     * Gets the box headers for the first "udta" box and
134
     * all parent boxes up to the top of the file as read by the
135
     * current instance.
136
     */
137
    public get udtaTree(): Mpeg4BoxHeader[] {
138
        return this._udtaTree;
×
139
    }
140

141
    /**
142
     * Gets all chunk offset boxes read by the current instance.
143
     */
144
    public get chunkOffsetBoxes(): Mpeg4Box[] {
145
        return this._stcoBoxes;
×
146
    }
147

148
    /**
149
     * Gets the position at which the mdat box starts.
150
     */
151
    public get mdatStartPosition(): number {
152
        return this._mdatStart;
30✔
153
    }
154

155
    /**
156
     * Gets the position at which the mdat box ends.
157
     */
158
    public get mdatEndPosition(): number {
159
        return this._mdatEnd;
30✔
160
    }
161

162
    /**
163
     * Get the User Data Box
164
     */
165
    public get userDataBox(): IsoUserDataBox {
166
        return this.userDataBoxes.length === 0 ? undefined : this.userDataBoxes[0];
×
167
    }
168

169
    /**
170
     * Parses the file referenced by the current instance,
171
     * searching for box headers that will be useful in saving
172
     * the file.
173
     */
174
    public parseBoxHeaders(): void {
175
        try {
×
176
            this.resetFields();
×
177
            this.parseBoxHeadersFromStartEndAndParents(this._firstHeader.totalBoxSize, this._file.length, undefined);
×
178
        } catch (e) {
179
            this._file.markAsCorrupt(e.message);
×
180
        }
181
    }
182

183
    /**
184
     * Parses the file referenced by the current instance, searching for tags.
185
     */
186
    public parseTag(): void {
187
        try {
30✔
188
            this.resetFields();
30✔
189
            this.parseTagFromStartEndAndParents(this._firstHeader.totalBoxSize, this._file.length, undefined);
30✔
190
        } catch (e) {
191
            this._file.markAsCorrupt(e.message);
×
192
        }
193
    }
194

195
    /**
196
     * Parses the file referenced by the current instance, searching for tags and properties.
197
     */
198
    public parseTagAndProperties(): void {
199
        try {
×
200
            this.resetFields();
×
201
            this.parseTagAndPropertiesFromStartEndHandlerAndParents(
×
202
                this._firstHeader.totalBoxSize,
203
                this._file.length,
204
                undefined,
205
                undefined
206
            );
207
        } catch (e) {
208
            this._file.markAsCorrupt(e.message);
×
209
        }
210
    }
211

212
    /**
213
     * Parses the file referenced by the current instance, searching for chunk offset boxes.
214
     */
215
    public parseChunkOffsets(): void {
216
        try {
×
217
            this.resetFields();
×
218
            this.ParseChunkOffsetsFromStartAndEnd(this._firstHeader.totalBoxSize, this._file.length);
×
219
        } catch (e) {
220
            this._file.markAsCorrupt(e.Message);
×
221
        }
222
    }
223

224
    /**
225
     * Parses boxes for a specified range, looking for headers.
226
     * @param start A value specifying the seek position at which to start reading.
227
     * @param end A value specifying the seek position at which to stop reading.
228
     * @param parents A @see Mpeg4BoxHeader[] object containing all the parent
229
     * handlers that apply to the range.
230
     */
231
    private parseBoxHeadersFromStartEndAndParents(start: number, end: number, parents: Mpeg4BoxHeader[]): void {
232
        let header: Mpeg4BoxHeader;
233

234
        for (let position = start; position < end; position += header.totalBoxSize) {
×
235
            header = Mpeg4BoxHeader.fromFileAndPosition(this._file, position);
×
236

237
            if (!this._moovTree && ByteVector.equals(header.boxType, Mpeg4BoxType.Moov)) {
×
238
                const newParents: Mpeg4BoxHeader[] = Mpeg4Utils.addParent(parents, header);
×
239
                this._moovTree = newParents;
×
240
                this.parseBoxHeadersFromStartEndAndParents(header.headerSize + position, header.totalBoxSize + position, newParents);
×
241
            } else if (
×
242
                ByteVector.equals(header.boxType, Mpeg4BoxType.Mdia) ||
×
243
                ByteVector.equals(header.boxType, Mpeg4BoxType.Minf) ||
244
                ByteVector.equals(header.boxType, Mpeg4BoxType.Stbl) ||
245
                ByteVector.equals(header.boxType, Mpeg4BoxType.Trak)
246
            ) {
247
                this.parseBoxHeadersFromStartEndAndParents(
×
248
                    header.headerSize + position,
249
                    header.totalBoxSize + position,
250
                    Mpeg4Utils.addParent(parents, header)
251
                );
252
            } else if (!this._udtaTree && ByteVector.equals(header.boxType, Mpeg4BoxType.Udta)) {
×
253
                // For compatibility, we still store the tree to the first udta
254
                // block. The proper way to get this info is from the individual
255
                // IsoUserDataBox.ParentTree member.
256
                this._udtaTree = Mpeg4Utils.addParent(parents, header);
×
257
            } else if (ByteVector.equals(header.boxType, Mpeg4BoxType.Mdat)) {
×
258
                this._mdatStart = position;
×
259
                this._mdatEnd = position + header.totalBoxSize;
×
260
            }
261

262
            if (header.totalBoxSize === 0) {
×
263
                break;
×
264
            }
265
        }
266
    }
267

268
    /**
269
     * Parses boxes for a specified range, looking for tags.
270
     * @param start A value specifying the seek position at which to start reading.
271
     * @param end A value specifying the seek position at which to stop reading.
272
     * @param parents A @see Mpeg4BoxHeader[] of @see Mpeg4BoxHeader parents.
273
     */
274
    private parseTagFromStartEndAndParents(start: number, end: number, parents: Mpeg4BoxHeader[]): void {
275
        let header: Mpeg4BoxHeader;
276

277
        for (let position = start; position < end; position += header.totalBoxSize) {
60✔
278
            header = Mpeg4BoxHeader.fromFileAndPosition(this._file, position);
30✔
279

280
            if (ByteVector.equals(header.boxType, Mpeg4BoxType.Moov)) {
30!
281
                this.parseTagFromStartEndAndParents(
30✔
282
                    header.headerSize + position,
283
                    header.totalBoxSize + position,
284
                    Mpeg4Utils.addParent(parents, header)
285
                );
286
            } else if (
×
287
                ByteVector.equals(header.boxType, Mpeg4BoxType.Mdia) ||
×
288
                ByteVector.equals(header.boxType, Mpeg4BoxType.Minf) ||
289
                ByteVector.equals(header.boxType, Mpeg4BoxType.Stbl) ||
290
                ByteVector.equals(header.boxType, Mpeg4BoxType.Trak)
291
            ) {
292
                this.parseTagFromStartEndAndParents(
×
293
                    header.headerSize + position,
294
                    header.totalBoxSize + position,
295
                    Mpeg4Utils.addParent(parents, header)
296
                );
297
            } else if (ByteVector.equals(header.boxType, Mpeg4BoxType.Udta)) {
×
298
                const udtaBox = Mpeg4BoxFactory.createBoxFromFileAndHeader(this._file, header) as IsoUserDataBox;
×
299

300
                // Since we can have multiple udta boxes, save the parent for each one
301
                const newParents: Mpeg4BoxHeader[] = Mpeg4Utils.addParent(parents, header);
×
302
                udtaBox.parentTree = newParents;
×
303

304
                this._udtaBoxes.push(udtaBox);
×
305
            } else if (ByteVector.equals(header.boxType, Mpeg4BoxType.Mdat)) {
×
306
                this._mdatStart = position;
×
307
                this._mdatEnd = position + header.totalBoxSize;
×
308
            }
309

310
            if (header.totalBoxSize === 0) {
30!
311
                break;
×
312
            }
313
        }
314
    }
315

316
    /**
317
     * Parses boxes for a specified range, looking for tags and properties.
318
     * @param start A value specifying the seek position at which to start reading.
319
     * @param end A value specifying the seek position at which to stop reading.
320
     * @param handler A @see IsoHandlerBox object that applied to the range being searched.
321
     * @param parents A @see Mpeg4BoxHeader[] of @see Mpeg4BoxHeader parents.
322
     */
323
    private parseTagAndPropertiesFromStartEndHandlerAndParents(
324
        start: number,
325
        end: number,
326
        handler: IsoHandlerBox,
327
        parents: Mpeg4BoxHeader[]
328
    ): void {
329
        let header: Mpeg4BoxHeader;
330

331
        for (let position = start; position < end; position += header.totalBoxSize) {
×
332
            header = Mpeg4BoxHeader.fromFileAndPosition(this._file, position);
×
333
            const type: ByteVector = header.boxType;
×
334

335
            if (ByteVector.equals(type, Mpeg4BoxType.Moov)) {
×
336
                this.parseTagAndPropertiesFromStartEndHandlerAndParents(
×
337
                    header.headerSize + position,
338
                    header.totalBoxSize + position,
339
                    handler,
340
                    Mpeg4Utils.addParent(parents, header)
341
                );
342
            } else if (
×
343
                ByteVector.equals(type, Mpeg4BoxType.Mdia) ||
×
344
                ByteVector.equals(type, Mpeg4BoxType.Minf) ||
345
                ByteVector.equals(type, Mpeg4BoxType.Stbl) ||
346
                ByteVector.equals(type, Mpeg4BoxType.Trak)
347
            ) {
348
                this.parseTagAndPropertiesFromStartEndHandlerAndParents(
×
349
                    header.headerSize + position,
350
                    header.totalBoxSize + position,
351
                    handler,
352
                    Mpeg4Utils.addParent(parents, header)
353
                );
354
            } else if (ByteVector.equals(type, Mpeg4BoxType.Stsd)) {
×
355
                this._stsdBoxes.push(Mpeg4BoxFactory.createBoxFromFileHeaderAndHandler(this._file, header, handler));
×
356
            } else if (ByteVector.equals(type, Mpeg4BoxType.Hdlr)) {
×
357
                handler = Mpeg4BoxFactory.createBoxFromFileHeaderAndHandler(this._file, header, handler) as IsoHandlerBox;
×
358
            } else if (!this._mvhdBox && ByteVector.equals(type, Mpeg4BoxType.Mvhd)) {
×
359
                this._mvhdBox = Mpeg4BoxFactory.createBoxFromFileHeaderAndHandler(this._file, header, handler) as IsoMovieHeaderBox;
×
360
            } else if (ByteVector.equals(type, Mpeg4BoxType.Udta)) {
×
361
                const udtaBox: IsoUserDataBox = Mpeg4BoxFactory.createBoxFromFileHeaderAndHandler(
×
362
                    this._file,
363
                    header,
364
                    handler
365
                ) as IsoUserDataBox;
366

367
                // Since we can have multiple udta boxes, save the parent for each one
368
                const newParents: Mpeg4BoxHeader[] = Mpeg4Utils.addParent(parents, header);
×
369
                udtaBox.parentTree = newParents;
×
370

371
                this._udtaBoxes.push(udtaBox);
×
372
            } else if (ByteVector.equals(type, Mpeg4BoxType.Mdat)) {
×
373
                this._mdatStart = position;
×
374
                this._mdatEnd = position + header.totalBoxSize;
×
375
            }
376

377
            if (header.totalBoxSize === 0) {
×
378
                break;
×
379
            }
380
        }
381
    }
382

383
    /**
384
     * Parses boxes for a specified range, looking for chunk offset boxes.
385
     * @param start A value specifying the seek position at which to start reading.
386
     * @param end A value specifying the seek position at which to stop reading.
387
     */
388
    private ParseChunkOffsetsFromStartAndEnd(start: number, end: number): void {
389
        let header: Mpeg4BoxHeader;
390

391
        for (let position = start; position < end; position += header.totalBoxSize) {
×
392
            header = Mpeg4BoxHeader.fromFileAndPosition(this._file, position);
×
393

394
            if (ByteVector.equals(header.boxType, Mpeg4BoxType.Moov)) {
×
395
                this.ParseChunkOffsetsFromStartAndEnd(header.headerSize + position, header.totalBoxSize + position);
×
396
            } else if (
×
397
                ByteVector.equals(header.boxType, Mpeg4BoxType.Moov) ||
×
398
                ByteVector.equals(header.boxType, Mpeg4BoxType.Mdia) ||
399
                ByteVector.equals(header.boxType, Mpeg4BoxType.Minf) ||
400
                ByteVector.equals(header.boxType, Mpeg4BoxType.Stbl) ||
401
                ByteVector.equals(header.boxType, Mpeg4BoxType.Trak)
402
            ) {
403
                this.ParseChunkOffsetsFromStartAndEnd(header.headerSize + position, header.totalBoxSize + position);
×
404
            } else if (
×
405
                ByteVector.equals(header.boxType, Mpeg4BoxType.Stco) ||
×
406
                ByteVector.equals(header.boxType, Mpeg4BoxType.Co64)
407
            ) {
408
                this._stcoBoxes.push(Mpeg4BoxFactory.createBoxFromFileAndHeader(this._file, header));
×
409
            } else if (ByteVector.equals(header.boxType, Mpeg4BoxType.Mdat)) {
×
410
                this._mdatStart = position;
×
411
                this._mdatEnd = position + header.totalBoxSize;
×
412
            }
413

414
            if (header.totalBoxSize === 0) {
×
415
                break;
×
416
            }
417
        }
418
    }
419

420
    /**
421
     * Resets all internal fields.
422
     */
423
    private resetFields(): void {
424
        this._mvhdBox = undefined;
30✔
425
        this._udtaBoxes = [];
30✔
426
        this._moovTree = undefined;
30✔
427
        this._udtaTree = undefined;
30✔
428
        this._stcoBoxes = [];
30✔
429
        this._stsdBoxes = [];
30✔
430
        this._mdatStart = -1;
30✔
431
        this._mdatEnd = -1;
30✔
432
    }
433
}
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

© 2025 Coveralls, Inc