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

benrr101 / node-taglib-sharp / 48390938

29 Oct 2023 03:32AM UTC coverage: 92.535% (+0.09%) from 92.443%
48390938

push

appveyor

benrr101
Cleanup docs for MPEG4

3244 of 4129 branches covered (0.0%)

Branch coverage included in aggregate %.

8 of 8 new or added lines in 3 files covered. (100.0%)

26727 of 28260 relevant lines covered (94.58%)

423.21 hits per line

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

31.41
/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
    /** @inheritDoc */
39
    public getTag(types: TagTypes, create: boolean): Tag {
40
        if (types === TagTypes.Apple) {
×
41
            if (!this._tag && create) {
×
42
                let udtaBox = this.findAppleTagUdta();
×
43
                if (!udtaBox) {
×
44
                    udtaBox = IsoUserDataBox.fromEmpty();
×
45
                }
46

47
                this._tag = new AppleTag(udtaBox);
×
48
            }
49

50
            return this._tag;
×
51
        }
52

53
        return undefined;
×
54
    }
55

56
    /** @inheritDoc */
57
    public removeTags(types: TagTypes): void {
58
        if (!NumberUtils.hasFlag(types, TagTypes.Apple) || !this._tag) {
×
59
            return;
×
60
        }
61

62
        this._tag.detachIlst();
×
63
        this._tag = undefined;
×
64
    }
65

66
    /** @inheritDoc */
67
    public save(): void {
68
        // Boilerplate
69
        this.preSave();
×
70

71
        if (this._udtaBoxes.length === 0) {
×
72
            const udtaBox = IsoUserDataBox.fromEmpty();
×
73
            this._udtaBoxes.push(udtaBox);
×
74
        }
75

76
        // Try to get into write mode.
77
        this.mode = FileAccessMode.Write;
×
78

79
        try {
×
80
            const parser = new Mpeg4FileParser(this);
×
81
            parser.parseBoxHeaders();
×
82

83
            let sizeChange: number;
84
            let writePosition: number;
85

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

93
            let udtaBox = this.findAppleTagUdta();
×
94

95
            if (!udtaBox) {
×
96
                udtaBox = IsoUserDataBox.fromEmpty();
×
97
            }
98

99
            const tagData = Mpeg4BoxRenderer.renderBox(udtaBox);
×
100

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

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

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

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

131
                for (const box of parser.chunkOffsetBoxes) {
×
132
                    if (box instanceof IsoChunkLargeOffsetBox) {
×
133
                        (<IsoChunkLargeOffsetBox>box).updatePosition(sizeChange, writePosition);
×
134

135
                        const updatedBox = Mpeg4BoxRenderer.renderBox(box);
×
136
                        this.insert(updatedBox, box.header.position, box.size);
×
137
                    } else if (box instanceof IsoChunkOffsetBox) {
×
138
                        (<IsoChunkOffsetBox>box).updatePositions(sizeChange, writePosition);
×
139

140
                        const updatedBox = Mpeg4BoxRenderer.renderBox(box);
×
141
                        this.insert(updatedBox, box.header.position, box.size);
×
142
                    }
143
                }
144
            }
145

146
            this.tagTypesOnDisk = this.tagTypes;
×
147
        } finally {
148
            this.mode = FileAccessMode.Closed;
×
149
        }
150
    }
151

152
    /**
153
     * Find the udta box within our collection that contains the Apple ILST data.
154
     * @returns IsoUserDataBox UDTA box within our collection that contains the Apple ILST data.
155
     */
156
    private findAppleTagUdta(): IsoUserDataBox {
157
        if (this._udtaBoxes.length === 1) {
1!
158
            return this._udtaBoxes[0]; // Single udta - just return it
1✔
159
        }
160

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

166
        if (possibleUdtaBoxes.length > 0) {
×
167
            return possibleUdtaBoxes[0];
×
168
        }
169

170
        return undefined;
×
171
    }
172

173
    /**
174
     * Gets if there is a udta with ILST present in our collection
175
     * @returns boolean `true` if there is a UDTA with ILST present in our collection
176
     */
177
    private isAppleTagUdtaPresent(): boolean {
178
        // @TODO: This can probably be replaced with a call to findAppleTagUdta
179
        for (const udtaBox of this._udtaBoxes) {
1✔
180
            if (udtaBox.getChild(Mpeg4BoxType.META)?.getChild(Mpeg4BoxType.ILST)) {
1!
181
                return true;
×
182
            }
183
        }
184
        return false;
1✔
185
    }
186

187
    private read(readStyle: ReadStyle): void {
188
        this.mode = FileAccessMode.Read;
1✔
189

190
        try {
1✔
191
            // Read the file
192
            const parser = new Mpeg4FileParser(this);
1✔
193

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

200
            this._udtaBoxes.push(...parser.userDataBoxes);
1✔
201

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

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

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

220
            this._tag = new AppleTag(udtaBox);
1✔
221

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

227
            // Get the movie header box.
228
            const mvhdBox = parser.movieHeaderBox;
×
229

230
            if (!mvhdBox) {
×
231
                throw new Error("mvhd box not found.");
×
232
            }
233

234
            const audioSampleEntry = parser.audioSampleEntry;
×
235
            const visualSampleEntry = parser.visualSampleEntry;
×
236

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

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