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

benrr101 / node-taglib-sharp / 51054818

25 Nov 2024 06:32PM UTC coverage: 92.556% (-0.02%) from 92.578%
51054818

push

appveyor

benrr101
Merge branch 'release/v6.0.0'

3251 of 4131 branches covered (78.7%)

Branch coverage included in aggregate %.

580 of 599 new or added lines in 27 files covered. (96.83%)

4 existing lines in 4 files now uncovered.

26752 of 28285 relevant lines covered (94.58%)

471.65 hits per line

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

31.21
/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

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

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

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

NEW
98
            const udtaHeader = udtaBox.parentTree[udtaBox.parentTree.length - 1];
×
NEW
99
            const totalBoxSize = udtaHeader.totalBoxSize;
×
NEW
100
            let writePosition = udtaHeader.position;
×
101

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

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

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

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

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

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

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

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

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

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

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

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

171
        return undefined;
×
172
    }
173

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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