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

benrr101 / node-taglib-sharp / 48387633

28 Oct 2023 05:34AM UTC coverage: 92.442% (+0.5%) from 91.899%
48387633

push

appveyor

web-flow
Merge 6a78b5713 into f0c9477b7

3250 of 4147 branches covered (0.0%)

Branch coverage included in aggregate %.

1763 of 1763 new or added lines in 39 files covered. (100.0%)

26753 of 28309 relevant lines covered (94.5%)

422.49 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
    private readonly _udtaBoxes: IsoUserDataBox[] = [];
1✔
19
    private _tag: AppleTag;
20
    private _properties: Properties;
21

22
    /** @inheritDoc */
23
    public constructor(file: IFileAbstraction | string, readStyle: ReadStyle) {
24
        super(file);
1✔
25
        this.read(readStyle);
1✔
26
    }
27

28
    /** @inheritDoc */
29
    public get tag(): AppleTag {
30
        return this._tag;
39✔
31
    }
32

33
    /** @inheritDoc */
34
    public get properties(): Properties {
35
        return this._properties;
×
36
    }
37

38
    protected get udtaBoxes(): IsoUserDataBox[] {
39
        return this._udtaBoxes;
2✔
40
    }
41

42
    /** @inheritDoc */
43
    public getTag(types: TagTypes, create: boolean): Tag {
44
        if (types === TagTypes.Apple) {
×
45
            if (!this._tag && create) {
×
46
                let udtaBox = this.findAppleTagUdta();
×
47
                if (!udtaBox) {
×
48
                    udtaBox = IsoUserDataBox.fromEmpty();
×
49
                }
50

51
                this._tag = new AppleTag(udtaBox);
×
52
            }
53

54
            return this._tag;
×
55
        }
56

57
        return undefined;
×
58
    }
59

60
    /** @inheritDoc */
61
    public removeTags(types: TagTypes): void {
62
        if (!NumberUtils.hasFlag(types, TagTypes.Apple) || !this._tag) {
×
63
            return;
×
64
        }
65

66
        this._tag.detachIlst();
×
67
        this._tag = undefined;
×
68
    }
69

70
    /** @inheritDoc */
71
    public save(): void {
72
        // Boilerplate
73
        this.preSave();
×
74

75
        if (this.udtaBoxes.length === 0) {
×
76
            const udtaBox = IsoUserDataBox.fromEmpty();
×
77
            this.udtaBoxes.push(udtaBox);
×
78
        }
79

80
        // Try to get into write mode.
81
        this.mode = FileAccessMode.Write;
×
82

83
        try {
×
84
            const parser = new Mpeg4FileParser(this);
×
85
            parser.parseBoxHeaders();
×
86

87
            let sizeChange: number;
88
            let writePosition: number;
89

90
            // To avoid rewriting udta blocks which might not have been modified,
91
            // the code here will work correctly if:
92
            // 1. There is a single udta for the entire file
93
            //   - OR -
94
            // 2. There are multiple utdtas, but only 1 of them contains the Apple ILST box.
95
            // We should be OK in the vast majority of cases
96

97
            let udtaBox = this.findAppleTagUdta();
×
98

99
            if (!udtaBox) {
×
100
                udtaBox = IsoUserDataBox.fromEmpty();
×
101
            }
102

103
            const tagData = Mpeg4BoxRenderer.renderBox(udtaBox);
×
104

105
            // If we don't have a "udta" box to overwrite...
106
            if (!udtaBox.parentTree || udtaBox.parentTree.length === 0) {
×
107
                // Stick the box at the end of the moov box.
108
                const moovHeader = parser.moovTree[parser.moovTree.length - 1];
×
109
                sizeChange = tagData.length;
×
110
                writePosition = moovHeader.position + moovHeader.totalBoxSize;
×
111
                this.insert(tagData, writePosition, 0);
×
112

113
                // Overwrite the parent box sizes.
114
                for (let i = parser.moovTree.length - 1; i >= 0; i--) {
×
115
                    sizeChange = parser.moovTree[i].overwrite(this, sizeChange);
×
116
                }
117
            } else {
118
                // Overwrite the old box.
119
                const udtaHeader = udtaBox.parentTree[udtaBox.parentTree.length - 1];
×
120
                sizeChange = tagData.length - udtaHeader.totalBoxSize;
×
121
                writePosition = udtaHeader.position;
×
122
                this.insert(tagData, writePosition, udtaHeader.totalBoxSize);
×
123

124
                // Overwrite the parent box sizes.
125
                for (let i = udtaBox.parentTree.length - 2; i >= 0; i--) {
×
126
                    sizeChange = udtaBox.parentTree[i].overwrite(this, sizeChange);
×
127
                }
128
            }
129

130
            // If we've had a size change, we may need to adjust chunk offsets.
131
            if (sizeChange !== 0) {
×
132
                // We may have moved the offset boxes, so we need to reread.
133
                parser.parseChunkOffsets();
×
134

135
                for (const box of parser.chunkOffsetBoxes) {
×
136
                    if (box instanceof IsoChunkLargeOffsetBox) {
×
137
                        (<IsoChunkLargeOffsetBox>box).updatePosition(sizeChange, writePosition);
×
138

139
                        const updatedBox = Mpeg4BoxRenderer.renderBox(box);
×
140
                        this.insert(updatedBox, box.header.position, box.size);
×
141
                    } else if (box instanceof IsoChunkOffsetBox) {
×
142
                        (<IsoChunkOffsetBox>box).updatePositions(sizeChange, writePosition);
×
143

144
                        const updatedBox = Mpeg4BoxRenderer.renderBox(box);
×
145
                        this.insert(updatedBox, box.header.position, box.size);
×
146
                    }
147
                }
148
            }
149

150
            this.tagTypesOnDisk = this.tagTypes;
×
151
        } finally {
152
            this.mode = FileAccessMode.Closed;
×
153
        }
154
    }
155

156
    /**
157
     * Find the udta box within our collection that contains the Apple ILST data.
158
     * @returns The udta box within our collection that contains the Apple ILST data.
159
     */
160
    private findAppleTagUdta(): IsoUserDataBox {
161
        if (this.udtaBoxes.length === 1) {
1!
162
            return this.udtaBoxes[0]; // Single udta - just return it
1✔
163
        }
164

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

170
        if (possibleUdtaBoxes.length > 0) {
×
171
            return possibleUdtaBoxes[0];
×
172
        }
173

174
        return undefined;
×
175
    }
176

177
    /**
178
     * Gets if there is a udta with ILST present in our collection
179
     * @returns True if there is a udta with ILST present in our collection
180
     */
181
    private isAppleTagUdtaPresent(): boolean {
182
        for (const udtaBox of this._udtaBoxes) {
1✔
183
            if (udtaBox.getChild(Mpeg4BoxType.META)?.getChild(Mpeg4BoxType.ILST)) {
1!
184
                return true;
×
185
            }
186
        }
187
        return false;
1✔
188
    }
189

190
    private read(readStyle: ReadStyle): void {
191
        this.mode = FileAccessMode.Read;
1✔
192

193
        try {
1✔
194
            // Read the file
195
            const parser = new Mpeg4FileParser(this);
1✔
196

197
            if ((readStyle & ReadStyle.Average) === 0) {
1!
198
                parser.parseTag();
1✔
199
            } else {
200
                parser.parseTagAndProperties();
×
201
            }
202

203
            this._udtaBoxes.push(...parser.userDataBoxes);
1✔
204

205
            // Ensure our collection contains at least a single empty box
206
            if (this._udtaBoxes.length === 0) {
1!
207
                const dummy = IsoUserDataBox.fromEmpty();
1✔
208
                this._udtaBoxes.push(dummy);
1✔
209
            }
210

211
            // Check if a udta with ILST actually exists
212
            if (this.isAppleTagUdtaPresent()) {
1!
213
                // There is an udta present with ILST info
214
                this.tagTypesOnDisk = NumberUtils.uintOr(this.tagTypesOnDisk, TagTypes.Apple);
×
215
            }
216

217
            // Find the udta box with the Apple Tag ILST
218
            let udtaBox = this.findAppleTagUdta();
1✔
219
            if (!udtaBox) {
1!
220
                udtaBox = IsoUserDataBox.fromEmpty();
×
221
            }
222

223
            this._tag = new AppleTag(udtaBox);
1✔
224

225
            // If we're not reading properties, we're done.
226
            if ((readStyle & ReadStyle.Average) === 0) {
1!
227
                return;
1✔
228
            }
229

230
            // Get the movie header box.
231
            const mvhdBox = parser.movieHeaderBox;
×
232

233
            if (!mvhdBox) {
×
234
                throw new Error("mvhd box not found.");
×
235
            }
236

237
            const audioSampleEntry = parser.audioSampleEntry;
×
238
            const visualSampleEntry = parser.visualSampleEntry;
×
239

240
            // Read the properties.
241
            this._properties = new Properties(mvhdBox.durationInMilliseconds, [audioSampleEntry, visualSampleEntry]);
×
242
        } finally {
243
            this.mode = FileAccessMode.Closed;
1✔
244
        }
245
    }
246
}
247

248
// /////////////////////////////////////////////////////////////////////////
249
// Register the file type
250
[
1✔
251
    "taglib/m4a",
252
    "taglib/m4b",
253
    "taglib/m4v",
254
    "taglib/m4p",
255
    "taglib/mp4",
256
    "audio/mp4",
257
    "audio/x-m4a",
258
    "video/mp4",
259
    "video/x-m4v"
260
].forEach(
261
    (mt) => File.addFileType(mt, Mpeg4File)
9✔
262
);
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