• 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

17.19
/src/mpeg4/mpeg4FileParser.ts
1
import IsoAudioSampleEntry from "./boxes/isoAudioSampleEntry";
1✔
2
import IsoHandlerBox from "./boxes/isoHandlerBox";
3
import IsoMovieHeaderBox from "./boxes/isoMovieHeaderBox";
4
import IsoUserDataBox from "./boxes/isoUserDataBox";
5
import IsoVisualSampleEntry from "./boxes/isoVisualSampleEntry";
1✔
6
import Mpeg4Box from "./boxes/mpeg4Box";
7
import Mpeg4BoxFactory from "./mpeg4BoxFactory";
1✔
8
import Mpeg4BoxHeader from "./mpeg4BoxHeader";
1✔
9
import Mpeg4BoxType from "./mpeg4BoxType";
1✔
10
import Mpeg4Utils from "./mpeg4Utils";
1✔
11
import { ByteVector } from "../byteVector";
1✔
12
import { File } from "../file";
13
import { Guards } from "../utils";
1✔
14

15
/**
16
 * This class provides methods for reading important information from an MPEG-4 file.
17
 */
18
export default class Mpeg4FileParser {
1✔
19
    /**
20
     * Contains the file to read from.
21
     */
22
    private readonly _file: File;
23

24
    /**
25
     * Contains the first header found in the file.
26
     */
27
    private readonly _firstHeader: Mpeg4BoxHeader;
28

29
    /**
30
     * Contains the ISO movie header box.
31
     */
32
    private _mvhdBox: IsoMovieHeaderBox;
33

34
    /**
35
     * Contains the ISO user data boxes.
36
     */
37
    private _udtaBoxes: IsoUserDataBox[] = [];
30✔
38

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

44
    /**
45
     * Contains the box headers from the top of the file to the "udta" box.
46
     */
47
    private _udtaTree: Mpeg4BoxHeader[];
48

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

54
    /**
55
     * Contains the "stsd" boxes found in the file.
56
     */
57
    private _stsdBoxes: Mpeg4Box[] = [];
30✔
58

59
    /**
60
     * Contains the position at which the "mdat" box starts.
61
     */
62
    private _mdatStart = -1;
30✔
63

64
    /**
65
     * Contains the position at which the "mdat" box ends.
66
     */
67
    private _mdatEnd = -1;
30✔
68

69
    /**
70
     * Constructs and initializes a new instance of {@see FileParser} for a specified file.
71
     * @param file A {@see File} object to perform operations on.
72
     */
73
    public constructor(file: File) {
74
        Guards.notNullOrUndefined(file, "File");
30✔
75

76
        this._file = file;
30✔
77
        this._firstHeader = Mpeg4BoxHeader.fromFileAndPosition(file, 0);
30✔
78

79
        if (!ByteVector.equals(this._firstHeader.boxType, Mpeg4BoxType.FTYP)) {
30!
80
            throw new Error("File does not start with 'ftyp' box.");
×
81
        }
82
    }
83

84
    /**
85
     * Gets the movie header box read by the current instance.
86
     */
87
    public get movieHeaderBox(): IsoMovieHeaderBox {
88
        return this._mvhdBox;
×
89
    }
90

91
    /**
92
     * Gets all user data boxes read by the current instance.
93
     */
94
    public get userDataBoxes(): IsoUserDataBox[] {
95
        return this._udtaBoxes;
30✔
96
    }
97

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

110
        return undefined;
×
111
    }
112

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

125
        return undefined;
×
126
    }
127

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

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

146
    /**
147
     * Gets all chunk offset boxes read by the current instance.
148
     */
149
    public get chunkOffsetBoxes(): Mpeg4Box[] {
150
        return this._stcoBoxes;
×
151
    }
152

153
    /**
154
     * Gets the position at which the mdat box starts.
155
     */
156
    public get mdatStartPosition(): number {
157
        return this._mdatStart;
×
158
    }
159

160
    /**
161
     * Gets the position at which the mdat box ends.
162
     */
163
    public get mdatEndPosition(): number {
164
        return this._mdatEnd;
×
165
    }
166

167
    /**
168
     * Get the User Data Box
169
     */
170
    public get userDataBox(): IsoUserDataBox {
171
        return this.userDataBoxes.length === 0 ? undefined : this.userDataBoxes[0];
×
172
    }
173

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

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

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

217
    /**
218
     * Parses the file referenced by the current instance, searching for chunk offset boxes.
219
     */
220
    public parseChunkOffsets(): void {
221
        try {
×
222
            this.resetFields();
×
223
            this.parseChunkOffsetsFromStartAndEnd(this._firstHeader.totalBoxSize, this._file.length);
×
224
        } catch (e) {
225
            this._file.markAsCorrupt(e.Message);
×
226
        }
227
    }
228

229
    /**
230
     * Parses boxes for a specified range, looking for headers.
231
     * @param start A value specifying the seek position at which to start reading.
232
     * @param end A value specifying the seek position at which to stop reading.
233
     * @param parents An array of {@see Mpeg4BoxHeader} containing all the parent handlers that
234
     *     apply to the range.
235
     */
236
    private parseBoxHeadersFromStartEndAndParents(start: number, end: number, parents: Mpeg4BoxHeader[]): void {
237
        let header: Mpeg4BoxHeader;
238

239
        for (let position = start; position < end; position += header.totalBoxSize) {
×
240
            header = Mpeg4BoxHeader.fromFileAndPosition(this._file, position);
×
241

242
            if (!this._moovTree && ByteVector.equals(header.boxType, Mpeg4BoxType.MOOV)) {
×
243
                const newParents = Mpeg4Utils.addParent(parents, header);
×
244
                this._moovTree = newParents;
×
245
                this.parseBoxHeadersFromStartEndAndParents(
×
246
                    header.headerSize + position,
247
                    header.totalBoxSize + position, newParents
248
                );
249
            } else if (
×
250
                ByteVector.equals(header.boxType, Mpeg4BoxType.MDIA) ||
×
251
                ByteVector.equals(header.boxType, Mpeg4BoxType.MINF) ||
252
                ByteVector.equals(header.boxType, Mpeg4BoxType.STBL) ||
253
                ByteVector.equals(header.boxType, Mpeg4BoxType.TRAK)
254
            ) {
255
                this.parseBoxHeadersFromStartEndAndParents(
×
256
                    header.headerSize + position,
257
                    header.totalBoxSize + position,
258
                    Mpeg4Utils.addParent(parents, header)
259
                );
260
            } else if (!this._udtaTree && ByteVector.equals(header.boxType, Mpeg4BoxType.UDTA)) {
×
261
                // For compatibility, we still store the tree to the first udta
262
                // block. The proper way to get this info is from the individual
263
                // IsoUserDataBox.ParentTree member.
264
                this._udtaTree = Mpeg4Utils.addParent(parents, header);
×
265
            } else if (ByteVector.equals(header.boxType, Mpeg4BoxType.MDAT)) {
×
266
                this._mdatStart = position;
×
267
                this._mdatEnd = position + header.totalBoxSize;
×
268
            }
269

270
            if (header.totalBoxSize === 0) {
×
271
                break;
×
272
            }
273
        }
274
    }
275

276
    /**
277
     * Parses boxes for a specified range, looking for tags.
278
     * @param start A value specifying the seek position at which to start reading.
279
     * @param end A value specifying the seek position at which to stop reading.
280
     * @param parents An array of {@see Mpeg4BoxHeader} parents.
281
     */
282
    private parseTagFromStartEndAndParents(start: number, end: number, parents: Mpeg4BoxHeader[]): void {
283
        let header: Mpeg4BoxHeader;
284

285
        for (let position = start; position < end; position += header.totalBoxSize) {
60✔
286
            header = Mpeg4BoxHeader.fromFileAndPosition(this._file, position);
30✔
287

288
            if (ByteVector.equals(header.boxType, Mpeg4BoxType.MOOV)) {
30!
289
                this.parseTagFromStartEndAndParents(
30✔
290
                    header.headerSize + position,
291
                    header.totalBoxSize + position,
292
                    Mpeg4Utils.addParent(parents, header)
293
                );
294
            } else if (
×
295
                ByteVector.equals(header.boxType, Mpeg4BoxType.MDIA) ||
×
296
                ByteVector.equals(header.boxType, Mpeg4BoxType.MINF) ||
297
                ByteVector.equals(header.boxType, Mpeg4BoxType.STBL) ||
298
                ByteVector.equals(header.boxType, Mpeg4BoxType.TRAK)
299
            ) {
300
                this.parseTagFromStartEndAndParents(
×
301
                    header.headerSize + position,
302
                    header.totalBoxSize + position,
303
                    Mpeg4Utils.addParent(parents, header)
304
                );
305
            } else if (ByteVector.equals(header.boxType, Mpeg4BoxType.UDTA)) {
×
306
                const udtaBox = Mpeg4BoxFactory.createBox(this._file, header) as IsoUserDataBox;
×
307

308
                // Since we can have multiple udta boxes, save the parent for each one
309
                udtaBox.parentTree = Mpeg4Utils.addParent(parents, header);
×
310

311
                this._udtaBoxes.push(udtaBox);
×
312
            } else if (ByteVector.equals(header.boxType, Mpeg4BoxType.MDAT)) {
×
313
                this._mdatStart = position;
×
314
                this._mdatEnd = position + header.totalBoxSize;
×
315
            }
316

317
            if (header.totalBoxSize === 0) {
30!
318
                break;
×
319
            }
320
        }
321
    }
322

323
    /**
324
     * Parses boxes for a specified range, looking for tags and properties.
325
     * @param start A value specifying the seek position at which to start reading.
326
     * @param end A value specifying the seek position at which to stop reading.
327
     * @param handler A {@see IsoHandlerBox} object that applied to the range being searched.
328
     * @param parents An array of {@see Mpeg4BoxHeader} parents.
329
     */
330
    private parseTagAndPropertiesFromStartEndHandlerAndParents(
331
        start: number,
332
        end: number,
333
        handler: IsoHandlerBox,
334
        parents: Mpeg4BoxHeader[]
335
    ): void {
336
        let header: Mpeg4BoxHeader;
337

338
        for (let position = start; position < end; position += header.totalBoxSize) {
×
339
            header = Mpeg4BoxHeader.fromFileAndPosition(this._file, position);
×
340
            const type = header.boxType;
×
341

342
            if (ByteVector.equals(type, Mpeg4BoxType.MOOV)) {
×
343
                this.parseTagAndPropertiesFromStartEndHandlerAndParents(
×
344
                    header.headerSize + position,
345
                    header.totalBoxSize + position,
346
                    handler,
347
                    Mpeg4Utils.addParent(parents, header)
348
                );
349
            } else if (
×
350
                ByteVector.equals(type, Mpeg4BoxType.MDIA) ||
×
351
                ByteVector.equals(type, Mpeg4BoxType.MINF) ||
352
                ByteVector.equals(type, Mpeg4BoxType.STBL) ||
353
                ByteVector.equals(type, Mpeg4BoxType.TRAK)
354
            ) {
355
                this.parseTagAndPropertiesFromStartEndHandlerAndParents(
×
356
                    header.headerSize + position,
357
                    header.totalBoxSize + position,
358
                    handler,
359
                    Mpeg4Utils.addParent(parents, header)
360
                );
361
            } else if (ByteVector.equals(type, Mpeg4BoxType.STSD)) {
×
362
                this._stsdBoxes.push(Mpeg4BoxFactory.createBox(
×
363
                    this._file,
364
                    header,
365
                    handler?.dataHandlerType
×
366
                ));
367
            } else if (ByteVector.equals(type, Mpeg4BoxType.HDLR)) {
×
368
                handler = <IsoHandlerBox>Mpeg4BoxFactory.createBox(
×
369
                    this._file,
370
                    header,
371
                    handler?.dataHandlerType
×
372
                );
373
            } else if (!this._mvhdBox && ByteVector.equals(type, Mpeg4BoxType.MVHD)) {
×
374
                this._mvhdBox = <IsoMovieHeaderBox>Mpeg4BoxFactory.createBox(
×
375
                    this._file,
376
                    header,
377
                    handler?.dataHandlerType
×
378
                );
379
            } else if (ByteVector.equals(type, Mpeg4BoxType.UDTA)) {
×
380
                const udtaBox = <IsoUserDataBox>Mpeg4BoxFactory.createBox(
×
381
                    this._file,
382
                    header,
383
                    handler?.dataHandlerType
×
384
                );
385

386
                // Since we can have multiple udta boxes, save the parent for each one
387
                udtaBox.parentTree = Mpeg4Utils.addParent(parents, header);
×
388

389
                this._udtaBoxes.push(udtaBox);
×
390
            } else if (ByteVector.equals(type, Mpeg4BoxType.MDAT)) {
×
391
                this._mdatStart = position;
×
392
                this._mdatEnd = position + header.totalBoxSize;
×
393
            }
394

395
            if (header.totalBoxSize === 0) {
×
396
                break;
×
397
            }
398
        }
399
    }
400

401
    /**
402
     * Parses boxes for a specified range, looking for chunk offset boxes.
403
     * @param start A value specifying the seek position at which to start reading.
404
     * @param end A value specifying the seek position at which to stop reading.
405
     */
406
    private parseChunkOffsetsFromStartAndEnd(start: number, end: number): void {
407
        let header: Mpeg4BoxHeader;
408

409
        for (let position = start; position < end; position += header.totalBoxSize) {
×
410
            header = Mpeg4BoxHeader.fromFileAndPosition(this._file, position);
×
411

412
            if (ByteVector.equals(header.boxType, Mpeg4BoxType.MOOV)) {
×
413
                this.parseChunkOffsetsFromStartAndEnd(header.headerSize + position, header.totalBoxSize + position);
×
414
            } else if (
×
415
                ByteVector.equals(header.boxType, Mpeg4BoxType.MOOV) ||
×
416
                ByteVector.equals(header.boxType, Mpeg4BoxType.MDIA) ||
417
                ByteVector.equals(header.boxType, Mpeg4BoxType.MINF) ||
418
                ByteVector.equals(header.boxType, Mpeg4BoxType.STBL) ||
419
                ByteVector.equals(header.boxType, Mpeg4BoxType.TRAK)
420
            ) {
421
                this.parseChunkOffsetsFromStartAndEnd(header.headerSize + position, header.totalBoxSize + position);
×
422
            } else if (
×
423
                ByteVector.equals(header.boxType, Mpeg4BoxType.STCO) ||
×
424
                ByteVector.equals(header.boxType, Mpeg4BoxType.CO64)
425
            ) {
426
                this._stcoBoxes.push(Mpeg4BoxFactory.createBox(this._file, header));
×
427
            } else if (ByteVector.equals(header.boxType, Mpeg4BoxType.MDAT)) {
×
428
                this._mdatStart = position;
×
429
                this._mdatEnd = position + header.totalBoxSize;
×
430
            }
431

432
            if (header.totalBoxSize === 0) {
×
433
                break;
×
434
            }
435
        }
436
    }
437

438
    /**
439
     * Resets all internal fields.
440
     */
441
    private resetFields(): void {
442
        this._mvhdBox = undefined;
30✔
443
        this._udtaBoxes = [];
30✔
444
        this._moovTree = undefined;
30✔
445
        this._udtaTree = undefined;
30✔
446
        this._stcoBoxes = [];
30✔
447
        this._stsdBoxes = [];
30✔
448
        this._mdatStart = -1;
30✔
449
        this._mdatEnd = -1;
30✔
450
    }
451
}
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