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

benrr101 / node-taglib-sharp / 54030989

12 May 2026 07:33PM UTC coverage: 92.455% (-0.09%) from 92.547%
54030989

push

appveyor

web-flow
Remove `.fromRawData()` static constructors from ID3v2 frames (#133)

* Remove AttachmentFrame.fromRawData()

* Remove CommentsFrame.fromRawData()

* :robot: Remove MusicCdIdentifierFrame.fromRawData()

* :robot: Remove PlayCountFrame.fromRawData()

* :robot: Remove PopularimeterFrame.fromRawData()

* :robot: Remove TermsOfUseFrame.fromRawData()

* :robot: Remove UniqueFileIdentifierFrame.fromRawData()

* :robot: Remove PrivateFrame.fromRawData()

* :robot: Remove UnknownFrame.fromRawData()

* :robot: Remove EventTimeCodeFrame.fromRawData()

* :robot: Remove UnsynchronizedLyricsFrame.fromRawData()

* :robot: Remove SynchronizedLyricsFrame.fromRawData()

* :robot: Remove RelativeVolumeFrame.fromRawData()

* :robot: Remove TextInformationFrame.fromRawData()

* :robot: Remove UserTextInformationFrame.fromRawData()

* :robot: Remove UrlLinkFrame.fromRawData()

* :robot: Remove UserUrlLinkFrame.fromRawData()

* :robot: Padding *before* header in fromOffsetRawData tests

* Remove stubbed fromRawData and the corresponding tests in FrameConstructorTests

* Fix wrong header in TOS frame test

3270 of 4161 branches covered (78.59%)

Branch coverage included in aggregate %.

107 of 107 new or added lines in 18 files covered. (100.0%)

5 existing lines in 3 files now uncovered.

26569 of 28113 relevant lines covered (94.51%)

475.01 hits per line

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

92.44
/src/id3v2/frames/frameHeader.ts
1
import SyncData from "../syncData";
1✔
2
import {ByteVector, StringType} from "../../byteVector";
1✔
3
import {CorruptFileError} from "../../errors";
1✔
4
import {FrameIdentifier, FrameIdentifiers} from "../frameIdentifiers";
1✔
5
import {Guards, NumberUtils} from "../../utils";
1✔
6

7
/**
8
 * Indicates the flags applied to a {@link Id3v2FrameHeader} object.
9
 */
10
export enum Id3v2FrameFlags {
1✔
11
    /**
12
     * Header contains no flags.
13
     */
14
    None = 0,
1✔
15

16
    /**
17
     * Frame is to be deleted if the tag is altered.
18
     */
19
    TagAlterPreservation = 0x4000,
1✔
20

21
    /**
22
     * Frame is to be deleted if the file is altered.
23
     */
24
    FileAlterPreservation = 0x2000,
1✔
25

26
    /**
27
     * Frame is read-only and should not be altered.
28
     */
29
    ReadOnly = 0x1000,
1✔
30

31
    /**
32
     * Frame has a grouping identity.
33
     */
34
    GroupingIdentity = 0x0040,
1✔
35

36
    /**
37
     * Frame data is compressed.
38
     */
39
    Compression = 0x0008,
1✔
40

41
    /**
42
     * Frame data is encrypted.
43
     */
44
    Encryption = 0x0004,
1✔
45

46
    /**
47
     * Frame data has been unsynchronized using the ID3v2 unsynchronization scheme.
48
     */
49
    Unsynchronized = 0x0002,
1✔
50

51
    /**
52
     * Frame has a data length indicator.
53
     */
54
    DataLengthIndicator = 0x0001
1✔
55
}
56

57
/**
58
 * This class provides a representation of an ID3v2 frame header which can be read from and
59
 * written to disk.
60
 */
61
export class Id3v2FrameHeader {
1✔
62
    private _flags: Id3v2FrameFlags;
63
    private _frameId: FrameIdentifier;
64
    private _frameSize: number;
65

66
    /**
67
     * Constructs and initializes a new instance by processing the data for the frame header.
68
     * @param id Identifier of the frame
69
     * @param flags Flags to assign to the frame (if omitted, defaults to
70
     *     {@link Id3v2FrameFlags.None})
71
     * @param frameSize Size of the frame in bytes, excluding the size of the header (if omitted,
72
     *     defaults to 0)
73
     */
74
    public constructor(id: FrameIdentifier, flags: Id3v2FrameFlags = Id3v2FrameFlags.None, frameSize: number = 0) {
1,639✔
75
        Guards.truthy(id, "id");
1,015✔
76
        Guards.uint(frameSize, "frameSize");
1,015✔
77

78
        this._frameId = id;
1,015✔
79
        this._frameSize = frameSize;
1,015✔
80
        this.flags = flags; // Force the compression/encryption checks
1,015✔
81
    }
82

83
    /**
84
     * Constructs and initializes a new instance of {@link Id3v2FrameHeader} by reading it from raw
85
     * header data of a specified version.
86
     * @param data Raw data to build the new instance from.
87
     *     If the data size is smaller than the size of a full header, the data is just treated as
88
     *     a frame identifier and the remaining values are zeroed.
89
     * @param version ID3v2 version with which the data in `data` was encoded.
90
     */
91
    public static fromData(data: ByteVector, version: number): Id3v2FrameHeader {
92
        Guards.truthy(data, "data");
106✔
93
        Guards.byte(version, "version");
104✔
94
        Guards.betweenInclusive(version, 2, 4, "version");
99✔
95

96
        if (data.length < (version === 2 ? 3 : 4)) {
97✔
97
            throw new Error("Data must contain at least a frame ID");
3✔
98
        }
99

100
        let rawFrameId: string;
101
        let frameId: FrameIdentifier;
102
        let flags = 0;
94✔
103
        let frameSize = 0;
94✔
104
        switch (version) {
94✔
105
            case 2:
106
                if (data.length < 3) {
2!
107
                    throw new CorruptFileError("Data must contain at least a 3 byte frame identifier");
×
108
                }
109

110
                // Set frame ID -- first 3 bytes
111
                rawFrameId = data.subarray(0, 3).toString(StringType.Latin1);
2✔
112
                frameId = FrameIdentifiers[rawFrameId] || new FrameIdentifier(undefined, undefined, rawFrameId);
2✔
113

114
                // If the full header information was not passed in, do not continue to the steps
115
                // to parse the frame size and flags.
116
                if (data.length < 6) {
2!
117
                    break;
2✔
118
                }
119

UNCOV
120
                frameSize = data.subarray(3, 3).toUint();
×
UNCOV
121
                break;
×
122

123
            case 3:
124
                if (data.length < 4) {
18!
125
                    throw new CorruptFileError("Data must contain at least a 4 byte frame identifier");
×
126
                }
127

128
                // Set the frame ID -- first 4 bytes
129
                rawFrameId = data.subarray(0, 4).toString(StringType.Latin1);
18✔
130
                frameId = FrameIdentifiers[rawFrameId] || new FrameIdentifier(undefined, rawFrameId, undefined);
18✔
131

132
                // If the full header information was not passed in, do not continue to the steps
133
                // to parse the frame size and flags.
134
                if (data.length < 10) {
18✔
135
                    break;
2✔
136
                }
137

138
                // Store the flags internally as version 2.4
139
                frameSize = data.subarray(4, 4).toUint();
16✔
140
                flags = NumberUtils.uintOr(
16✔
141
                    NumberUtils.uintAnd(NumberUtils.uintLShift(data.get(8), 7), 0x7000),
142
                    NumberUtils.uintAnd(NumberUtils.uintRShift(data.get(9), 4), 0x000C),
143
                    NumberUtils.uintAnd(NumberUtils.uintLShift(data.get(9), 1), 0x0040)
144
                );
145
                break;
16✔
146

147
            case 4:
148
                if (data.length < 4) {
74!
149
                    throw new CorruptFileError("Data must contain at least 4 byte frame identifier");
×
150
                }
151

152
                // Set the frame ID -- the first 4 bytes
153
                rawFrameId = data.subarray(0, 4).toString(StringType.Latin1);
74✔
154
                frameId = FrameIdentifiers[rawFrameId] || new FrameIdentifier(rawFrameId, undefined, undefined);
74✔
155

156
                // If the full header information was not passed in, do not continue to the steps to
157
                // ... eh, you probably get it by now.
158
                if (data.length < 10) {
74✔
159
                    break;
2✔
160
                }
161

162
                frameSize = SyncData.toUint(data.subarray(4, 4));
72✔
163
                flags = data.subarray(8, 2).toUshort();
72✔
164
                break;
72✔
165
        }
166

167
        return new Id3v2FrameHeader(frameId, flags, frameSize);
94✔
168
    }
169

170
    /**
171
     * Constructs and initializes a new, blank frame header of size 0, with the
172
     * provided frame identifier.
173
     * @param id Identifier for the frame
174
     */
175
    public static fromFrameIdentifier(id: FrameIdentifier): Id3v2FrameHeader {
176
        return new Id3v2FrameHeader(id, Id3v2FrameFlags.None, 0);
24✔
177
    }
178

179
    // #region Properties
180

181
    /**
182
     * Gets the flags applied to the current instance.
183
     */
184
    public get flags(): Id3v2FrameFlags { return this._flags; }
4,068✔
185
    /**
186
     * Sets the flags applied to the current instance.
187
     */
188
    public set flags(value: Id3v2FrameFlags) {
189
        if (NumberUtils.hasFlag(value, (Id3v2FrameFlags.Compression | Id3v2FrameFlags.Encryption))) {
1,108✔
190
            throw new Error("Argument invalid: Encryption and compression are not supported");
3✔
191
        }
192
        this._flags = value;
1,105✔
193
    }
194

195
    /**
196
     * Gets the identifier of the frame described by the current instance.
197
     */
198
    public get frameId(): FrameIdentifier { return this._frameId; }
3,017✔
199
    /**
200
     * Sets the identifier of the frame described by the current instance.
201
     */
202
    public set frameId(value: FrameIdentifier) {
203
        Guards.truthy(value, "value");
5✔
204
        this._frameId = value;
5✔
205
    }
206

207
    /**
208
     * Gets the size of the frame described by the current instance, minus the header.
209
     */
210
    public get frameSize(): number { return this._frameSize; }
465✔
211
    /**
212
     * Sets the size of the frame described by the current instance, minus the header.
213
     * Must be a positive, safe integer.
214
     */
215
    public set frameSize(value: number) {
216
        Guards.uint(value, "value");
598✔
217
        this._frameSize = value;
598✔
218
    }
219

220
    // #endregion
221

222
    // #region Public Methods
223

224
    /**
225
     * Gets the size of a header for a specified ID3v2 version.
226
     * @param version Version of ID3v2 to get the size for. Must be a positive integer < 256
227
     */
228
    public static getSize(version: number): number {
229
        Guards.byte(version, "version");
430✔
230
        return version < 3 ? 6 : 10;
430✔
231
    }
232

233
    /**
234
     * Renders the current instance, encoded in a specified ID3v2 version.
235
     * @param version Version of ID3v2 to use when encoding the current instance.
236
     */
237
    public render(version: number): ByteVector {
238
        Guards.byte(version, "version");
560✔
239
        Guards.betweenInclusive(version, 2, 4, "version");
560✔
240

241
        // Start by rendering the frame identifier
242
        const byteVectors = [this._frameId.render(version)];
560✔
243

244
        switch (version) {
558✔
245
            case 2:
246
                byteVectors.push(ByteVector.fromUint(this._frameSize).subarray(1, 3));
58✔
247
                break;
58✔
248

249
            case 3:
250
                const newFlags = NumberUtils.uintOr(
96✔
251
                    NumberUtils.uintAnd(NumberUtils.uintLShift(this._flags, 1), 0xE000),
252
                    NumberUtils.uintAnd(NumberUtils.uintLShift(this._flags, 4), 0x00C0),
253
                    NumberUtils.uintAnd(NumberUtils.uintRShift(this._flags, 1), 0x0020)
254
                );
255

256
                byteVectors.push(ByteVector.fromUint(this._frameSize));
96✔
257
                byteVectors.push(ByteVector.fromUshort(newFlags));
96✔
258
                break;
96✔
259

260
            case 4:
261
                byteVectors.push(SyncData.fromUint(this._frameSize));
404✔
262
                byteVectors.push(ByteVector.fromUshort(this._flags));
404✔
263
                break;
404✔
264
        }
265

266
        return ByteVector.concatenate(... byteVectors);
558✔
267
    }
268

269
    // #endregion
270
}
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