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

benrr101 / node-taglib-sharp / 48391054

29 Oct 2023 04:39AM UTC coverage: 92.535% (-1.4%) from 93.934%
48391054

push

appveyor

benrr101
Merge branch 'release/v5.2.0'

3244 of 4129 branches covered (0.0%)

Branch coverage included in aggregate %.

2177 of 2177 new or added lines in 61 files covered. (100.0%)

26728 of 28261 relevant lines covered (94.58%)

423.2 hits per line

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

17.93
/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
 * @internal
18
 */
19
export default class Mpeg4FileParser {
1✔
20
    /**
21
     * Contains the file to read from.
22
     */
23
    private readonly _file: File;
24

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

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

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

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

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

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

55
    /**
56
     * Constructs and initializes a new instance of {{@link FileParser}} for a specified file.
57
     * @param file A {{@link File}} object to perform operations on.
58
     */
59
    public constructor(file: File) {
60
        Guards.truthy(file, "File");
1✔
61

62
        this._file = file;
1✔
63
        this._firstHeader = Mpeg4BoxHeader.fromFileAndPosition(file, 0);
1✔
64

65
        if (!ByteVector.equals(this._firstHeader.boxType, Mpeg4BoxType.FTYP)) {
1!
66
            throw new Error("File does not start with 'ftyp' box.");
×
67
        }
68
    }
69

70
    /**
71
     * Gets the movie header box read by the current instance.
72
     */
73
    public get movieHeaderBox(): IsoMovieHeaderBox { return this._mvhdBox; }
×
74

75
    /**
76
     * Gets all user data boxes read by the current instance.
77
     */
78
    public get userDataBoxes(): IsoUserDataBox[] { return this._udtaBoxes; }
1✔
79

80
    /**
81
     * Gets the audio sample entry read by the current instance.
82
     */
83
    public get audioSampleEntry(): IsoAudioSampleEntry {
84
        for (const box of this._stsdBoxes) {
×
85
            for (const sub of box.children) {
×
86
                if (sub instanceof IsoAudioSampleEntry) {
×
87
                    return sub;
×
88
                }
89
            }
90
        }
91

92
        return undefined;
×
93
    }
94

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

107
        return undefined;
×
108
    }
109

110
    /**
111
     * Gets the box headers for the first "moov" box and
112
     * all parent boxes up to the top of the file as read by the
113
     * current instance.
114
     */
115
    public get moovTree(): Mpeg4BoxHeader[] { return this._moovTree; }
×
116

117
    /**
118
     * Gets all chunk offset boxes read by the current instance.
119
     */
120
    public get chunkOffsetBoxes(): Mpeg4Box[] { return this._stcoBoxes; }
×
121

122
    /**
123
     * Parses the file referenced by the current instance,
124
     * searching for box headers that will be useful in saving
125
     * the file.
126
     */
127
    public parseBoxHeaders(): void {
128
        try {
×
129
            this.resetFields();
×
130
            this.parseBoxHeadersFromStartEndAndParents(this._firstHeader.totalBoxSize, this._file.length, undefined);
×
131
        } catch (e) {
132
            this._file.markAsCorrupt(e.message);
×
133
        }
134
    }
135

136
    /**
137
     * Parses the file referenced by the current instance, searching for tags.
138
     */
139
    public parseTag(): void {
140
        try {
1✔
141
            this.resetFields();
1✔
142
            this.parseTagFromStartEndAndParents(this._firstHeader.totalBoxSize, this._file.length, undefined);
1✔
143
        } catch (e) {
144
            this._file.markAsCorrupt(e.message);
×
145
        }
146
    }
147

148
    /**
149
     * Parses the file referenced by the current instance, searching for tags and properties.
150
     */
151
    public parseTagAndProperties(): void {
152
        try {
×
153
            this.resetFields();
×
154
            this.parseTagAndPropertiesFromStartEndHandlerAndParents(
×
155
                this._firstHeader.totalBoxSize,
156
                this._file.length,
157
                undefined,
158
                undefined
159
            );
160
        } catch (e) {
161
            this._file.markAsCorrupt(e.message);
×
162
        }
163
    }
164

165
    /**
166
     * Parses the file referenced by the current instance, searching for chunk offset boxes.
167
     */
168
    public parseChunkOffsets(): void {
169
        try {
×
170
            this.resetFields();
×
171
            this.parseChunkOffsetsFromStartAndEnd(this._firstHeader.totalBoxSize, this._file.length);
×
172
        } catch (e) {
173
            this._file.markAsCorrupt(e.Message);
×
174
        }
175
    }
176

177
    /**
178
     * Parses boxes for a specified range, looking for headers.
179
     * @param start A value specifying the seek position at which to start reading.
180
     * @param end A value specifying the seek position at which to stop reading.
181
     * @param parents An array of {{@link Mpeg4BoxHeader}} containing all the parent handlers that
182
     *     apply to the range.
183
     */
184
    private parseBoxHeadersFromStartEndAndParents(start: number, end: number, parents: Mpeg4BoxHeader[]): void {
185
        let header: Mpeg4BoxHeader;
186

187
        for (let position = start; position < end; position += header.totalBoxSize) {
×
188
            header = Mpeg4BoxHeader.fromFileAndPosition(this._file, position);
×
189

190
            if (!this._moovTree && ByteVector.equals(header.boxType, Mpeg4BoxType.MOOV)) {
×
191
                const newParents = Mpeg4Utils.addParent(parents, header);
×
192
                this._moovTree = newParents;
×
193
                this.parseBoxHeadersFromStartEndAndParents(
×
194
                    header.headerSize + position,
195
                    header.totalBoxSize + position, newParents
196
                );
197
            } else if (
×
198
                ByteVector.equals(header.boxType, Mpeg4BoxType.MDIA) ||
×
199
                ByteVector.equals(header.boxType, Mpeg4BoxType.MINF) ||
200
                ByteVector.equals(header.boxType, Mpeg4BoxType.STBL) ||
201
                ByteVector.equals(header.boxType, Mpeg4BoxType.TRAK)
202
            ) {
203
                this.parseBoxHeadersFromStartEndAndParents(
×
204
                    header.headerSize + position,
205
                    header.totalBoxSize + position,
206
                    Mpeg4Utils.addParent(parents, header)
207
                );
208
            }
209

210
            if (header.totalBoxSize === 0) {
×
211
                break;
×
212
            }
213
        }
214
    }
215

216
    /**
217
     * Parses boxes for a specified range, looking for tags.
218
     * @param start A value specifying the seek position at which to start reading.
219
     * @param end A value specifying the seek position at which to stop reading.
220
     * @param parents An array of {{@link Mpeg4BoxHeader}} parents.
221
     */
222
    private parseTagFromStartEndAndParents(start: number, end: number, parents: Mpeg4BoxHeader[]): void {
223
        let header: Mpeg4BoxHeader;
224

225
        for (let position = start; position < end; position += header.totalBoxSize) {
2✔
226
            header = Mpeg4BoxHeader.fromFileAndPosition(this._file, position);
1✔
227

228
            if (ByteVector.equals(header.boxType, Mpeg4BoxType.MOOV)) {
1!
229
                this.parseTagFromStartEndAndParents(
1✔
230
                    header.headerSize + position,
231
                    header.totalBoxSize + position,
232
                    Mpeg4Utils.addParent(parents, header)
233
                );
234
            } else if (
×
235
                ByteVector.equals(header.boxType, Mpeg4BoxType.MDIA) ||
×
236
                ByteVector.equals(header.boxType, Mpeg4BoxType.MINF) ||
237
                ByteVector.equals(header.boxType, Mpeg4BoxType.STBL) ||
238
                ByteVector.equals(header.boxType, Mpeg4BoxType.TRAK)
239
            ) {
240
                this.parseTagFromStartEndAndParents(
×
241
                    header.headerSize + position,
242
                    header.totalBoxSize + position,
243
                    Mpeg4Utils.addParent(parents, header)
244
                );
245
            } else if (ByteVector.equals(header.boxType, Mpeg4BoxType.UDTA)) {
×
246
                const udtaBox = Mpeg4BoxFactory.createBox(this._file, header) as IsoUserDataBox;
×
247

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

251
                this._udtaBoxes.push(udtaBox);
×
252
            }
253

254
            if (header.totalBoxSize === 0) {
1!
255
                break;
×
256
            }
257
        }
258
    }
259

260
    /**
261
     * Parses boxes for a specified range, looking for tags and properties.
262
     * @param start A value specifying the seek position at which to start reading.
263
     * @param end A value specifying the seek position at which to stop reading.
264
     * @param handler A {{@link IsoHandlerBox}} object that applied to the range being searched.
265
     * @param parents An array of {{@link Mpeg4BoxHeader}} parents.
266
     */
267
    private parseTagAndPropertiesFromStartEndHandlerAndParents(
268
        start: number,
269
        end: number,
270
        handler: IsoHandlerBox,
271
        parents: Mpeg4BoxHeader[]
272
    ): void {
273
        let header: Mpeg4BoxHeader;
274

275
        for (let position = start; position < end; position += header.totalBoxSize) {
×
276
            header = Mpeg4BoxHeader.fromFileAndPosition(this._file, position);
×
277
            const type = header.boxType;
×
278

279
            if (ByteVector.equals(type, Mpeg4BoxType.MOOV)) {
×
280
                this.parseTagAndPropertiesFromStartEndHandlerAndParents(
×
281
                    header.headerSize + position,
282
                    header.totalBoxSize + position,
283
                    handler,
284
                    Mpeg4Utils.addParent(parents, header)
285
                );
286
            } else if (
×
287
                ByteVector.equals(type, Mpeg4BoxType.MDIA) ||
×
288
                ByteVector.equals(type, Mpeg4BoxType.MINF) ||
289
                ByteVector.equals(type, Mpeg4BoxType.STBL) ||
290
                ByteVector.equals(type, Mpeg4BoxType.TRAK)
291
            ) {
292
                this.parseTagAndPropertiesFromStartEndHandlerAndParents(
×
293
                    header.headerSize + position,
294
                    header.totalBoxSize + position,
295
                    handler,
296
                    Mpeg4Utils.addParent(parents, header)
297
                );
298
            } else if (ByteVector.equals(type, Mpeg4BoxType.STSD)) {
×
299
                this._stsdBoxes.push(Mpeg4BoxFactory.createBox(
×
300
                    this._file,
301
                    header,
302
                    handler?.dataHandlerType
×
303
                ));
304
            } else if (ByteVector.equals(type, Mpeg4BoxType.HDLR)) {
×
305
                handler = <IsoHandlerBox>Mpeg4BoxFactory.createBox(
×
306
                    this._file,
307
                    header,
308
                    handler?.dataHandlerType
×
309
                );
310
            } else if (!this._mvhdBox && ByteVector.equals(type, Mpeg4BoxType.MVHD)) {
×
311
                this._mvhdBox = <IsoMovieHeaderBox>Mpeg4BoxFactory.createBox(
×
312
                    this._file,
313
                    header,
314
                    handler?.dataHandlerType
×
315
                );
316
            } else if (ByteVector.equals(type, Mpeg4BoxType.UDTA)) {
×
317
                const udtaBox = <IsoUserDataBox>Mpeg4BoxFactory.createBox(
×
318
                    this._file,
319
                    header,
320
                    handler?.dataHandlerType
×
321
                );
322

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

326
                this._udtaBoxes.push(udtaBox);
×
327
            }
328

329
            if (header.totalBoxSize === 0) {
×
330
                break;
×
331
            }
332
        }
333
    }
334

335
    /**
336
     * Parses boxes for a specified range, looking for chunk offset boxes.
337
     * @param start A value specifying the seek position at which to start reading.
338
     * @param end A value specifying the seek position at which to stop reading.
339
     */
340
    private parseChunkOffsetsFromStartAndEnd(start: number, end: number): void {
341
        let header: Mpeg4BoxHeader;
342

343
        for (let position = start; position < end; position += header.totalBoxSize) {
×
344
            header = Mpeg4BoxHeader.fromFileAndPosition(this._file, position);
×
345

346
            if (ByteVector.equals(header.boxType, Mpeg4BoxType.MOOV)) {
×
347
                this.parseChunkOffsetsFromStartAndEnd(header.headerSize + position, header.totalBoxSize + position);
×
348
            } else if (
×
349
                ByteVector.equals(header.boxType, Mpeg4BoxType.MOOV) ||
×
350
                ByteVector.equals(header.boxType, Mpeg4BoxType.MDIA) ||
351
                ByteVector.equals(header.boxType, Mpeg4BoxType.MINF) ||
352
                ByteVector.equals(header.boxType, Mpeg4BoxType.STBL) ||
353
                ByteVector.equals(header.boxType, Mpeg4BoxType.TRAK)
354
            ) {
355
                this.parseChunkOffsetsFromStartAndEnd(header.headerSize + position, header.totalBoxSize + position);
×
356
            } else if (
×
357
                ByteVector.equals(header.boxType, Mpeg4BoxType.STCO) ||
×
358
                ByteVector.equals(header.boxType, Mpeg4BoxType.CO64)
359
            ) {
360
                this._stcoBoxes.push(Mpeg4BoxFactory.createBox(this._file, header));
×
361
            }
362

363
            if (header.totalBoxSize === 0) {
×
364
                break;
×
365
            }
366
        }
367
    }
368

369
    /**
370
     * Resets all internal fields.
371
     */
372
    private resetFields(): void {
373
        this._mvhdBox = undefined;
1✔
374
        this._udtaBoxes = [];
1✔
375
        this._moovTree = undefined;
1✔
376
        this._stcoBoxes = [];
1✔
377
        this._stsdBoxes = [];
1✔
378
    }
379
}
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