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

benrr101 / node-taglib-sharp / 46462138

pending completion
46462138

push

appveyor

Benjamin Russell
Merge branch 'release/v5.1.0' into develop

3096 of 3788 branches covered (81.73%)

Branch coverage included in aggregate %.

42 of 42 new or added lines in 12 files covered. (100.0%)

25320 of 26463 relevant lines covered (95.68%)

437.0 hits per line

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

92.9
/src/id3v2/frames/frameFactory.ts
1
import AttachmentFrame from "./attachmentFrame";
1✔
2
import CommentsFrame from "./commentsFrame";
1✔
3
import MusicCdIdentifierFrame from "./musicCdIdentifierFrame";
1✔
4
import PlayCountFrame from "./playCountFrame";
1✔
5
import PopularimeterFrame from "./popularimeterFrame";
1✔
6
import PrivateFrame from "./privateFrame";
1✔
7
import TermsOfUseFrame from "./termsOfUseFrame";
1✔
8
import UniqueFileIdentifierFrame from "./uniqueFileIdentifierFrame";
1✔
9
import UnknownFrame from "./unknownFrame";
1✔
10
import UnsynchronizedLyricsFrame from "./unsynchronizedLyricsFrame";
1✔
11
import {ByteVector} from "../../byteVector";
1✔
12
import {CorruptFileError, NotImplementedError} from "../../errors";
1✔
13
import {EventTimeCodeFrame} from "./eventTimeCodeFrame";
1✔
14
import {File} from "../../file";
15
import {Frame} from "./frame";
16
import {Id3v2FrameFlags, Id3v2FrameHeader} from "./frameHeader";
1✔
17
import {FrameIdentifiers} from "../frameIdentifiers";
1✔
18
import {RelativeVolumeFrame} from "./relativeVolumeFrame";
1✔
19
import {SynchronizedLyricsFrame} from "./synchronizedLyricsFrame";
1✔
20
import {TextInformationFrame, UserTextInformationFrame} from "./textInformationFrame";
1✔
21
import {UrlLinkFrame, UserUrlLinkFrame} from "./urlLinkFrame";
1✔
22
import {Guards, NumberUtils} from "../../utils";
1✔
23

24
/**
25
 * Type shortcut for a method that returns a {@link Frame}.
26
 * @param data Byte vector that contains the frame
27
 * @param offset Position into the byte vector where the frame begins
28
 * @param header The header that describes the frame
29
 * @param version ID3v2 version the frame is encoded with. Must be unsigned 8-bit int
30
 */
31
export type FrameCreator = (data: ByteVector, offset: number, header: Id3v2FrameHeader, version: number) => Frame;
32

33
let customFrameCreators: FrameCreator[] = [];
1✔
34

35
/**
36
 * Performs the necessary operations to determine and create the correct child classes of
37
 * {@link Frame} for a given raw ID3v2 frame.
38
 * By default, this will only load frames contained in the library. To add additional frames to the
39
 * process, register a frame creator with {@link addFrameCreator}.
40
 */
41
export default {
1✔
42
    /**
43
     * Adds a custom frame creator to try before using standard frame creation methods.
44
     * Frame creators are used before standard methods so custom checking can be used and new
45
     * formats can be added. They are executed in reverse order in which they are added.
46
     * @param creator Frame creator function
47
     *     * data: ByteVector Raw ID3v2 frame
48
     *     * offset: number Offset in data at which the frame data begins (should be int)
49
     *     * header: Id3v2FrameHeader Header for the frame contained in data
50
     *     * version: number ID3v2 version the raw frame data is stored in (should be byte)
51
     *     * returns Frame if method was able to match the frame, falsy otherwise
52
     */
53
    addFrameCreator: (creator: FrameCreator): void => {
54
        Guards.truthy(creator, "creator");
2✔
55
        customFrameCreators.unshift(creator);
2✔
56
    },
57

58
    /**
59
     * Removes all custom frame creators
60
     */
61
    clearFrameCreators: (): void => {
62
        customFrameCreators = [];
2✔
63
    },
64

65
    /**
66
     * Creates a {@link Frame} object by reading it from raw ID3v2 frame data.
67
     * @param data Raw ID3v2 frame
68
     * @param file File to read the frame from if `data` is falsy
69
     * @param offset Index into `file` or in `data` if truthy, at which the
70
     *     frame begins. After reading, the offset where the next frame can be read is returned in
71
     *     the `offset` property of the returned object
72
     * @param version ID3v2 version the frame is encoded with. Must be unsigned 8-bit int
73
     * @param alreadyUnsynced Whether or not the entire tag has already been unsynchronized
74
     * @returns
75
     *     Undefined is returned if there are no more frames to read.
76
     *     Object is returned if a frame was found. Object has the following properties:
77
     *     * frame: {@link Frame} that was read
78
     *     * offset: updated offset where the next frame starts
79
     */
80
    // @TODO: Split into fromFile and fromData
81
    createFrame: (
82
        data: ByteVector,
83
        file: File,
84
        offset: number,
85
        version: number,
86
        alreadyUnsynced: boolean
87
    ): {frame: Frame, offset: number} => {
88
        Guards.uint(offset, "offset");
124✔
89
        Guards.byte(version, "version");
121✔
90

91
        let position = 0;
118✔
92
        const frameHeaderSize = Id3v2FrameHeader.getSize(version);
118✔
93

94
        if (!data && !file) {
118✔
95
            throw new Error("Argument exception: data or file must be provided");
2✔
96
        }
97

98
        if (!data) {
116✔
99
            file.seek(offset);
39✔
100
            data = file.readBlock(frameHeaderSize);
39✔
101
        } else {
102
            file = undefined;
77✔
103
            position = offset;
77✔
104
        }
105

106
        // If the next data's position is 0, assume that we've hit the padding portion of the frame
107
        if (data.get(position) === 0) {
116✔
108
            return undefined;
28✔
109
        }
110

111
        const header = Id3v2FrameHeader.fromData(data.subarray(position, frameHeaderSize), version);
88✔
112
        const frameStartIndex = offset + frameHeaderSize;
86✔
113
        const frameEndIndex = offset + header.frameSize + frameHeaderSize;
86✔
114
        const frameSize = frameEndIndex - frameStartIndex;
86✔
115

116
        // Illegal frames are filtered out when creating the frame header
117

118
        // Mark the frame as unsynchronized if the entire tag is already unsynchronized
119
        if (alreadyUnsynced) {
86✔
120
            header.flags &= ~Id3v2FrameFlags.Unsynchronized;
1✔
121
        }
122

123
        // TODO: Support compression
124
        if (NumberUtils.hasFlag(header.flags, Id3v2FrameFlags.Compression)) {
86!
125
            throw new NotImplementedError("Compression is not supported");
×
126
        }
127

128
        // TODO: Support encryption
129
        if (NumberUtils.hasFlag(header.flags, Id3v2FrameFlags.Encryption)) {
86!
130
            throw new NotImplementedError("Encryption is not supported");
×
131
        }
132

133
        try {
86✔
134
            // Try to find a custom creator
135
            for (const creator of customFrameCreators) {
86✔
136
                // @TODO: If we're reading from a file, data will only ever contain the header
137
                const frame = creator(data, position, header, version);
2✔
138
                if (frame) {
2✔
139
                    return {
1✔
140
                        frame: frame,
141
                        offset: frameEndIndex
142
                    };
143
                }
144
            }
145

146
            // This is where things get necessarily nasty. Here we determine which frame subclass (or
147
            // if none is found, simply a frame) based on the frame ID. Since there are a lot of
148
            // possibilities, that means a lot of if statements.
149

150
            // Lazy object loading handling
151
            if (file) {
85✔
152
                // Attached picture (frames 4.14)
153
                // General encapsulated object (frames 4.15)
154
                // TODO: Make lazy loading optional
155
                if (header.frameId === FrameIdentifiers.APIC || header.frameId === FrameIdentifiers.GEOB) {
21✔
156
                    return {
2✔
157
                        frame: AttachmentFrame.fromFile(
158
                            file.fileAbstraction,
159
                            header,
160
                            frameStartIndex,
161
                            frameSize,
162
                            version
163
                        ),
164
                        offset: frameEndIndex
165
                    };
166
                }
167

168
                // Read remaining part of the frame for the non lazy Frame
169
                file.seek(frameStartIndex);
19✔
170
                data = ByteVector.concatenate(
19✔
171
                    data,
172
                    file.readBlock(frameSize)
173
                );
174
            }
175

176
            let func: FrameCreator;
177
            if (header.frameId === FrameIdentifiers.TXXX) {
83✔
178
                // User text identification frame
179
                func = UserTextInformationFrame.fromOffsetRawData;
23✔
180
            } else if (header.frameId.isTextFrame) {
60✔
181
                // Text identification frame (frames 4.2)
182
                func = TextInformationFrame.fromOffsetRawData;
33✔
183
            } else if (header.frameId === FrameIdentifiers.UFID) {
27✔
184
                // Unique file identifier (frames 4.1)
185
                func = UniqueFileIdentifierFrame.fromOffsetRawData;
6✔
186
            } else if (header.frameId === FrameIdentifiers.MCDI) {
21✔
187
                // Music CD identifier (frames 4.5)
188
                func = MusicCdIdentifierFrame.fromOffsetRawData;
1✔
189
            } else if (header.frameId === FrameIdentifiers.USLT) {
20✔
190
                // Unsynchronized lyrics (frames 4.8)
191
                func = UnsynchronizedLyricsFrame.fromOffsetRawData;
1✔
192
            } else if (header.frameId === FrameIdentifiers.SYLT) {
19✔
193
                // Synchronized lyrics (frames 4.8)
194
                func = SynchronizedLyricsFrame.fromOffsetRawData;
1✔
195
            } else if (header.frameId === FrameIdentifiers.COMM) {
18✔
196
                // Comments (frames 4.10)
197
                func = CommentsFrame.fromOffsetRawData;
1✔
198
            } else if (header.frameId === FrameIdentifiers.RVA2) {
17✔
199
                // Relative volume adjustment (frames 4.11)
200
                func = RelativeVolumeFrame.fromOffsetRawData;
1✔
201
            } else if (header.frameId === FrameIdentifiers.APIC || header.frameId === FrameIdentifiers.GEOB) {
16✔
202
                // Attached picture (frames 4.14)
203
                func = AttachmentFrame.fromOffsetRawData;
2✔
204
            } else if (header.frameId === FrameIdentifiers.PCNT) {
14✔
205
                // Play count (frames 4.16)
206
                func = PlayCountFrame.fromOffsetRawData;
7✔
207
            } else if (header.frameId === FrameIdentifiers.POPM) {
7✔
208
                // Popularimeter (frames 4.17)
209
                func = PopularimeterFrame.fromOffsetRawData;
1✔
210
            } else if (header.frameId === FrameIdentifiers.USER) {
6✔
211
                // Terms of Use (frames 4.22)
212
                func = TermsOfUseFrame.fromOffsetRawData;
1✔
213
            } else if (header.frameId === FrameIdentifiers.PRIV) {
5✔
214
                // Private (frames 4.27)
215
                func = PrivateFrame.fromOffsetRawData;
1✔
216
            } else if (header.frameId === FrameIdentifiers.WXXX) {
4✔
217
                // User URL link
218
                func = UserUrlLinkFrame.fromOffsetRawData;
1✔
219
            } else if (header.frameId.isUrlFrame) {
3✔
220
                // URL link (frame 4.3.1)
221
                func = UrlLinkFrame.fromOffsetRawData;
1✔
222
            } else if (header.frameId === FrameIdentifiers.ETCO) {
2✔
223
                // Event timing codes (frames 4.6)
224
                func = EventTimeCodeFrame.fromOffsetRawData;
1✔
225
            } else {
226
                // Return unknown
227
                func = UnknownFrame.fromOffsetRawData;
1✔
228
            }
229

230
            return {
83✔
231
                frame: func(data, position, header, version),
232
                offset: frameEndIndex
233
            };
234
        } catch (e: unknown) {
235
            if (e instanceof CorruptFileError || e instanceof NotImplementedError) {
×
236
                throw e;
×
237
            }
238

239
            // Other exceptions will just mean we ignore the frame
240
            return {
×
241
                frame: undefined,
242
                offset: frameEndIndex
243
            };
244
        }
245
    }
246
};
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