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

benrr101 / node-taglib-sharp / 46462135

pending completion
46462135

push

appveyor

Benjamin Russell
Merge branch 'release/v5.1.0'

3096 of 3788 branches covered (81.73%)

Branch coverage included in aggregate %.

2171 of 2171 new or added lines in 47 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

95.27
/src/id3v2/frames/urlLinkFrame.ts
1
import {ByteVector, StringType} from "../../byteVector";
1✔
2
import {Frame, FrameClassType} from "./frame";
1✔
3
import {Id3v2FrameHeader} from "./frameHeader";
1✔
4
import {FrameIdentifier, FrameIdentifiers} from "../frameIdentifiers";
1✔
5
import {Guards} from "../../utils";
1✔
6

7
/**
8
 * Provides ID3v2 URL Link frame implementation (section 4.3.1) covering `W000` to `WZZZ`,
9
 * excluding `WXXX`.
10
 * With these frames dynamic data such as webpages with touring information, price information,
11
 * or plain ordinary news can be added to the tag. There may only be one URL link frame of its kind
12
 * in a tag, except when stated otherwise in the frame description. If the text string is followed
13
 * by a string termination, all the following information should be ignored and not be displayed.
14
 * The following table contains the types and descriptions as found in the ID3 2.4.0 native frames
15
 * specification.
16
 * * WCOM - The 'Commercial Information' frame is a URL pointing at a webpage with information
17
 *   such as where the album can be bought. There may be more than one WCOM frame per tag, but not
18
 *   with the same content.
19
 * * WCOP - The 'Copyright/Legal information' frame is a URL pointing at a webpage where the terms
20
 *   of use and ownership of the field is described.
21
 * * WOAF - The 'Official audio file webpage' frame is a URL pointing at a file specific webpage.
22
 * * WOAR - The 'Official artist/performer webpage' frame is a URL pointing at the artists'
23
 *   official webpage. There may be more than one WOAR frame in a tag if the audio contains more
24
 *   than one performer, but not with the same content.
25
 * * WOAS - THe 'Official audio source webpage' frame is a URL pointing at the official webpage of
26
 *   the source of the audio file, eg. a movie.
27
 * * WORS - The 'Official internet radio station homepage' frame contains a URL pointing at the
28
 *   homepage of the internet radio station.
29
 * * WPAY - The 'Payment' frame is a URL pointing at a webpage that will handle the process of
30
 *   paying for this file.
31
 * * WPUB - The 'Publisher's official webpage' frame is a URL pointing at the official webpage
32
 *   for the publisher.
33
 */
34
export class UrlLinkFrame extends Frame {
1✔
35
    // @TODO: Don't allow protected member variables
36
    protected _encoding: StringType = StringType.Latin1;
52✔
37
    protected _rawData: ByteVector;
38
    protected _rawVersion: number;
39
    protected _textFields: string[] = [];
52✔
40

41
    // #region Constructors
42

43
    protected constructor(header: Id3v2FrameHeader) {
44
        super(header);
52✔
45
    }
46

47
    /**
48
     * Constructs and initializes an empty frame with the provided frame identity
49
     * @param ident Identity of the frame to construct
50
     */
51
    public static fromIdentity(ident: FrameIdentifier): UrlLinkFrame {
52
        Guards.truthy(ident, "ident");
13✔
53
        return new UrlLinkFrame(new Id3v2FrameHeader(ident));
11✔
54
    }
55

56
    /**
57
     * Constructs and initializes a new instance by reading its raw data in a specified ID3v2
58
     * version. This method allows for offset reading from the data byte vector.
59
     * @param data Raw representation of the new frame
60
     * @param offset What offset in `data` the frame actually begins. Must be positive,
61
     *     safe integer
62
     * @param header Header of the frame found at `data` in the data
63
     * @param version ID3v2 version the frame was originally encoded with
64
     */
65
    public static fromOffsetRawData(
66
        data: ByteVector,
67
        offset: number,
68
        header: Id3v2FrameHeader,
69
        version: number
70
    ): UrlLinkFrame {
71
        Guards.truthy(data, "data");
12✔
72
        Guards.uint(offset, "offset");
10✔
73
        Guards.truthy(header, "header");
5✔
74
        Guards.byte(version, "version");
3✔
75

76
        const frame = new UrlLinkFrame(header);
3✔
77
        frame.setData(data, offset, false, version);
3✔
78
        return frame;
3✔
79
    }
80

81
    /**
82
     * Constructs and initializes a new instance by reading its raw data in a specified
83
     * ID3v2 version.
84
     * @param data Raw representation of the new frame
85
     * @param version ID3v2 version the raw frame is encoded with, must be a positive 8-bit integer
86
     */
87
    public static fromRawData(data: ByteVector, version: number): UrlLinkFrame {
88
        Guards.truthy(data, "data");
19✔
89
        Guards.byte(version, "version");
17✔
90

91
        const frame = new UrlLinkFrame(Id3v2FrameHeader.fromData(data, version));
14✔
92
        frame.setData(data, 0, true, version);
14✔
93
        return frame;
14✔
94
    }
95

96
    // #endregion
97

98
    // #region Properties
99

100
    public get frameClassType(): FrameClassType { return FrameClassType.UrlLinkFrame; }
10✔
101

102
    /**
103
     * Gets the text contained in the current instance.
104
     * Modifying the contents of the returned value will not modify the contents of the current
105
     * instance. The value must be reassigned for the value to change.
106
     */
107
    public get text(): string[] {
108
        this.parseRawData();
65✔
109
        return this._textFields.slice(0);
65✔
110
    }
111
    /**
112
     * Sets the text contained in the current instance.
113
     */
114
    public set text(value: string[]) {
115
        this._rawData = undefined;
19✔
116
        this._textFields = value ? value.slice() : [];
19✔
117
    }
118

119
    /**
120
     * Gets the text encoding to use when rendering the current instance.
121
     */
122
    public get textEncoding(): StringType {
123
        this.parseRawData();
19✔
124
        return this._encoding;
19✔
125
    }
126
    /**
127
     * Sets the text encoding to use when rendering the current instance.
128
     * NOTE: This value will be overwritten if {@link Id3v2Settings.forceDefaultEncoding} is `true`.
129
     * @param value
130
     */
131
    public set textEncoding(value: StringType) { this._encoding = value; }
4✔
132

133
    // #endregion
134

135
    // #region Methods
136

137
    /**
138
     * Gets the first frame that matches the provided type
139
     * @param frames Object to search in
140
     * @param ident Frame identifier to search for
141
     * @returns Frame containing the matching frameId, `undefined` if a match was not found
142
     */
143
    public static findUrlLinkFrame(frames: UrlLinkFrame[], ident: FrameIdentifier): UrlLinkFrame {
144
        Guards.truthy(frames, "frames");
10✔
145
        Guards.truthy(ident, "ident");
8✔
146

147
        return frames.find((f) => f.frameId === ident);
6✔
148
    }
149

150
    /** @inheritDoc */
151
    public clone(): UrlLinkFrame {
152
        const frame = UrlLinkFrame.fromIdentity(this.frameId);
2✔
153
        frame._textFields = this._textFields.slice();
2✔
154
        frame._rawData = this._rawData?.toByteVector();
2✔
155
        frame._rawVersion = this._rawVersion;
2✔
156
        return frame;
2✔
157
    }
158

159
    /**
160
     * Generates a string representation of the URL link frame.
161
     */
162
    public toString(): string {
163
        this.parseRawData();
1✔
164
        return this.text.join("; ");
1✔
165
    }
166

167
    /** @inheritDoc */
168
    protected parseFields(data: ByteVector, version: number): void {
169
        Guards.byte(version, "version");
38✔
170
        this._rawData = data.toByteVector();
38✔
171
        this._rawVersion = version;
38✔
172
    }
173

174
    /**
175
     * Performs the actual parsing of the raw data.
176
     * @remarks
177
     *     Because of the high parsing cost and relatively low usage of the class,
178
     *     {@link parseFields} only stores the field data, so it can be parsed on demand. Whenever
179
     *     a property or method is called which requires the data, this method is called, and only
180
     *     on the first call does it actually parse the data.
181
     * @protected
182
     */
183
    protected parseRawData(): void {
184
        if (!this._rawData) {
85✔
185
            return;
58✔
186
        }
187

188
        const data = this._rawData;
27✔
189
        this._rawData = undefined;
27✔
190

191
        const fieldList = [];
27✔
192
        let index = 0;
27✔
193
        if (this.frameId === FrameIdentifiers.WXXX && data.length > 0) {
27✔
194
            // Text Encoding    $xx
195
            // Description      <text string according to encoding> $00 (00)
196
            // URL              <text string>
197
            const encoding = <StringType> data.get(index);
23✔
198
            const delim = ByteVector.getTextDelimiter(encoding);
23✔
199
            index++;
23✔
200

201
            const delimIndex = data.offsetFind(delim, index, delim.length);
23✔
202
            if (delimIndex >= 0) {
23!
203
                const descriptionLength = delimIndex - index;
23✔
204
                const description = data.subarray(index, descriptionLength).toString(encoding);
23✔
205
                fieldList.push(description);
23✔
206
                index += descriptionLength + delim.length;
23✔
207
            }
208
        }
209

210
        if (index < data.length) {
27✔
211

212
            // Read the url from the data
213
            let url = data.subarray(index).toString(StringType.Latin1);
25✔
214
            url = url.replace(/[\s\0]+$/, "");
25✔
215

216
            fieldList.push(url);
25✔
217
        }
218
        this._textFields = fieldList;
27✔
219
    }
220

221
    /** @inheritDoc */
222
    protected renderFields(version: number): ByteVector {
223
        // @TODO: Move WXXX rendering to WXXX class
224
        if (this._rawData && this._rawVersion === version) {
4✔
225
            return this._rawData;
2✔
226
        }
227

228
        const encoding = UrlLinkFrame.correctEncoding(this.textEncoding, version);
2✔
229
        const isWxxx = this.frameId === FrameIdentifiers.WXXX;
2✔
230

231
        let textFields = this._textFields;
2✔
232
        if (version > 3 || isWxxx) {
2!
233
            if (isWxxx) {
2✔
234
                if (textFields.length === 0) {
1!
235
                    textFields = [undefined, undefined];
×
236
                } else if (textFields.length === 1) {
1!
237
                    textFields = [textFields[0], undefined];
×
238
                }
239
            }
240
        }
241
        // @TODO: is this correct formatting?
242
        const text = textFields.join("/");
2✔
243

244
        return ByteVector.concatenate(
2✔
245
            isWxxx ? encoding : undefined,
2✔
246
            ByteVector.fromString(text, StringType.Latin1)
247
        );
248
    }
249

250
    // #endregion
251
}
252

253
/**
254
 * Provides support for ID3v2 User URL Link frames (WXXX).
255
 */
256
export class UserUrlLinkFrame extends UrlLinkFrame {
1✔
257
    // #region Constructors
258

259
    private constructor(header: Id3v2FrameHeader) {
260
        super(header);
24✔
261
    }
262

263
    /**
264
     * Constructs and initializes a new instance using the provided description as the text
265
     * of the frame.
266
     * @param description Description to use as text of the frame.
267
     */
268
    public static fromDescription(description: string): UserUrlLinkFrame {
269
        const frame = new UserUrlLinkFrame(new Id3v2FrameHeader(FrameIdentifiers.WXXX));
3✔
270
        frame.text = [description];
3✔
271
        return frame;
3✔
272
    }
273

274
    /**
275
     * Constructs and initializes a new instance by reading its raw data in a specified ID3v2
276
     * version. This method allows for offset reading from the data byte vector.
277
     * @param data Raw representation of the new frame
278
     * @param offset What offset in `data` the frame actually begins. Must be positive,
279
     *     safe integer
280
     * @param header Header of the frame found at `data` in the data
281
     * @param version ID3v2 version the frame was originally encoded with
282
     */
283
    public static fromOffsetRawData(
284
        data: ByteVector,
285
        offset: number,
286
        header: Id3v2FrameHeader,
287
        version: number
288
    ): UserUrlLinkFrame {
289
        Guards.truthy(data, "data");
11✔
290
        Guards.uint(offset, "offset");
9✔
291
        Guards.truthy(header, "header");
4✔
292

293
        const frame = new UserUrlLinkFrame(header);
2✔
294
        frame.setData(data, offset, false, version);
2✔
295
        return frame;
2✔
296
    }
297

298
    /**
299
     * Constructs and initializes a new instance by reading its raw data in a specified
300
     * ID3v2 version.
301
     * @param data Raw representation of the new frame
302
     * @param version ID3v2 version the raw frame is encoded with, must be a positive 8-bit integer
303
     */
304
    public static fromRawData(data: ByteVector, version: number): UserUrlLinkFrame {
305
        Guards.truthy(data, "data");
24✔
306
        Guards.byte(version, "version");
22✔
307

308
        const frame = new UserUrlLinkFrame(Id3v2FrameHeader.fromData(data, version));
19✔
309
        frame.setData(data, 0, true, version);
19✔
310
        return frame;
19✔
311
    }
312

313
    // #endregion
314

315
    // #region Properties
316

317
    /** @inheritDoc */
318
    public get frameClassType(): FrameClassType { return FrameClassType.UserUrlLinkFrame; }
3✔
319

320
    /**
321
     * Gets the description stored in the current instance.
322
     */
323
    public get description(): string {
324
        const text = super.text;
25✔
325
        return text.length > 0 ? text[0] : undefined;
25✔
326
    }
327
    /**
328
     * Sets the description stored in the current instance.
329
     * There should only be one frame with a matching description per tag.
330
     */
331
    public set description(value: string) {
332
        const normalizedValue = value || undefined;
4✔
333

334
        let text = super.text;
4✔
335
        if (text.length > 0) {
4✔
336
            text[0] = normalizedValue;
3✔
337
        } else {
338
            text = [normalizedValue];
1✔
339
        }
340
        super.text = text;
4✔
341
    }
342

343
    /**
344
     * Gets the text contained in the current instance.
345
     * NOTE: Modifying the contents of the returned value will not modify the contents of the
346
     * current instance. The value must be reassigned for the value to change.
347
     */
348
    public get text(): string[] {
349
        const text = super.text;
17✔
350
        if (text.length < 2) { return []; }
17✔
351

352
        const newText = new Array<string>(text.length - 1);
14✔
353
        for (let i = 0; i < newText.length; i++) {
14✔
354
            newText[i] = text[i + 1];
16✔
355
        }
356
        return newText;
14✔
357
    }
358
    /**
359
     * Sets the text contained in the current instance.
360
     */
361
    public set text(value: string[]) {
362
        const newValue = [this.description];
6✔
363
        if (value) {
6✔
364
            newValue.push(... value);
4✔
365
        }
366
        super.text = newValue;
6✔
367
    }
368

369
    // #endregion
370

371
    // #region Methods
372

373
    /**
374
     * Gets a frame from a list of frames.
375
     * @param frames List of frames to search
376
     * @param description Description of the frame to match
377
     * @returns Frame containing the matching user, `undefined` if a match was not found
378
     */
379
    public static findUserUrlLinkFrame(frames: UserUrlLinkFrame[], description: string): UserUrlLinkFrame {
380
        Guards.truthy(frames, "frames");
7✔
381
        Guards.truthy(description, "description");
5✔
382

383
        return frames.find((f) => f.description === description);
3✔
384
    }
385

386
    /** @inheritDoc */
387
    public clone(): UserUrlLinkFrame {
388
        const frame = UserUrlLinkFrame.fromDescription(undefined);
2✔
389
        frame._encoding = this._encoding;
2✔
390
        frame._textFields = this._textFields.slice();
2✔
391
        frame._rawData = this._rawData?.toByteVector();
2✔
392
        frame._rawVersion = this._rawVersion;
2✔
393
        return frame;
2✔
394
    }
395

396
    /** @inheritDoc */
397
    public toString(): string {
398
        return `[${this.description}] ${super.toString()}`;
×
399
    }
400

401
    // #endregion
402
}
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