• 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

98.4
/src/id3v2/frames/commentsFrame.ts
1
import Id3v2Settings from "../id3v2Settings";
1✔
2
import {ByteVector, StringType} from "../../byteVector";
1✔
3
import {CorruptFileError} from "../../errors";
1✔
4
import {Frame, FrameClassType} from "./frame";
1✔
5
import {Id3v2FrameHeader} from "./frameHeader";
1✔
6
import {FrameIdentifiers} from "../frameIdentifiers";
1✔
7
import {Guards} from "../../utils";
1✔
8

9
/**
10
 * Class that extends {@link Frame}, implementing support for ID3v2 Comments (COMM) frames.
11
 * A {@link CommentsFrame} should be used for storing user readable comments on the media file.
12
 * When reading comments from a file, {@link CommentsFrame.findPreferred} should be used as it
13
 * gracefully falls back to comments that you, as a developer, may not be expecting.
14
 */
15
export default class CommentsFrame extends Frame {
1✔
16
    private _description: string;
17
    private _language: string;
18
    private _text: string;
19
    private _textEncoding: StringType = Id3v2Settings.defaultEncoding;
60✔
20

21
    // #region
22

23
    private constructor(frameHeader: Id3v2FrameHeader) {
24
        super(frameHeader);
60✔
25
    }
26

27
    /**
28
     * Constructs and initializes a new CommentsFrame from a description
29
     * @param description Description of the new frame
30
     * @param language Optional, ISO-639-2 language code for the new frame
31
     * @param encoding Optional, text encoding to use when rendering the new frame
32
     */
33
    public static fromDescription(
34
        description: string,
35
        language?: string,
36
        encoding: StringType = Id3v2Settings.defaultEncoding
43✔
37
    ): CommentsFrame {
38
        Guards.notNullOrUndefined(description, "description");
46✔
39

40
        const frame = new CommentsFrame(new Id3v2FrameHeader(FrameIdentifiers.COMM));
44✔
41
        frame.textEncoding = encoding;
44✔
42
        frame._language = language;
44✔
43
        frame._description = description;
44✔
44

45
        return frame;
44✔
46
    }
47

48
    /**
49
     * Constructs and initializes a new CommentsFrame by reading its raw data in a specified ID3v2
50
     * version. This method allows for offset reading from the data byte vector.
51
     * @param data Raw representation of the new frame
52
     * @param offset What offset in `data` the frame actually begins. Must be positive,
53
     *     safe integer
54
     * @param header Header of the frame found at `data` in the data
55
     * @param version ID3v2 version the frame was originally encoded with
56
     */
57
    public static fromOffsetRawData(
58
        data: ByteVector,
59
        offset: number,
60
        header: Id3v2FrameHeader,
61
        version: number
62
    ): CommentsFrame {
63
        Guards.truthy(data, "data");
15✔
64
        Guards.uint(offset, "offset");
13✔
65
        Guards.truthy(header, "header");
8✔
66
        Guards.byte(version, "version");
6✔
67

68
        const frame = new CommentsFrame(header);
6✔
69
        frame.setData(data, offset, false, version);
6✔
70
        return frame;
5✔
71
    }
72

73
    /**
74
     * Constructs and initializes a new CommentsFrame by reading its raw data in a specified
75
     * ID3v2 version.
76
     * @param data Raw representation of the new frame
77
     * @param version ID3v2 version the raw frame is encoded with, must be a positive 8-bit integer
78
     * @param version ID3v2 version the frame was originally encoded with
79
     */
80
    public static fromRawData(data: ByteVector, version: number): CommentsFrame {
81
        Guards.truthy(data, "data");
15✔
82
        Guards.byte(version, "version");
13✔
83

84
        const frame = new CommentsFrame(Id3v2FrameHeader.fromData(data, version));
10✔
85
        frame.setData(data, 0, true, version);
10✔
86
        return frame;
9✔
87
    }
88

89
    // #endregion
90

91
    // #region Public Properties
92

93
    /** @inheritDoc */
94
    public get frameClassType(): FrameClassType { return FrameClassType.CommentsFrame; }
23✔
95

96
    /**
97
     * Gets the description stored in the current instance, or empty string if not set.
98
     */
99
    public get description(): string { return this._description || ""; }
73✔
100
    /**
101
     * Sets the description stored in the current instance.
102
     * There should only be one frame with a matching description and ISO-639-2 language code per
103
     * tag.
104
     * @param value Description of the instance
105
     */
106
    public set description(value: string) { this._description = value; }
3✔
107

108
    /**
109
     * Gets the ISO-639-2 language code stored in the current instance or 'XXX' if not set
110
     */
111
    public get language(): string {
112
        return this._language && this._language.length > 2
44✔
113
            ? this._language.substring(0, 3)
44✔
114
            : "XXX";
115
    }
116
    /**
117
     * Sets the ISO-639-2 language code stored in the current instance
118
     * @param value Language code to store
119
     */
120
    public set language(value: string) { this._language = value; }
5✔
121

122
    /**
123
     * Gets the comment text stored in the current instance, or empty string if not set.
124
     */
125
    public get text(): string { return this._text || ""; }
24✔
126
    /**
127
     * Sets the comment text stored in the current instance.
128
     * @param value Comment text to store
129
     */
130
    public set text(value: string) { this._text = value; }
12✔
131

132
    /**
133
     * Gets the text encoding to use when storing the current instance.
134
     */
135
    public get textEncoding(): StringType { return this._textEncoding; }
30✔
136
    /**
137
     * Sets the text encoding to use when storing the current instance.
138
     * @param value Text encoding to use when storing the current instance
139
     */
140
    public set textEncoding(value: StringType) { this._textEncoding = value; }
59✔
141

142
    // #endregion
143

144
    /**
145
     * Gets a comment frame that matched the provided parameters from the list of frames
146
     * @param frames Frames to search for best matching frame
147
     * @param description Description of the comments frame to match
148
     * @param language Optional, ISO-639-2 language code to match
149
     * @returns Object containing the matching frame or `undefined` if a match was not found
150
     */
151
    public static find(frames: CommentsFrame[], description: string, language?: string): CommentsFrame {
152
        Guards.truthy(frames, "frames");
5✔
153

154
        return frames.find((f) => {
3✔
155
            if (f.description !== description) { return false; }
9✔
156
            // noinspection RedundantIfStatementJS
157
            if (language && f.language !== language) { return false; }
4✔
158
            return true;
2✔
159
        });
160
    }
161

162
    /**
163
     * Gets all comment frames that match the provided parameters from the list of frames
164
     * @param frames Frames to search
165
     * @param description Description of the comments frame to match
166
     * @param language Optional, ISO-639-2 language code to match
167
     * @returns
168
     *     Array of comments frames that match the provided parameters or an
169
     *     empty array if none were found
170
     */
171
    public static findAll(frames: CommentsFrame[], description: string, language?: string): CommentsFrame[] {
172
        Guards.truthy(frames, "frames");
5✔
173

174
        return frames.filter((f) => {
3✔
175
            if (f.description !== description) { return false; }
11✔
176
            // noinspection RedundantIfStatementJS
177
            if (language && f.language !== language) { return false; }
5✔
178
            return true;
3✔
179
        });
180
    }
181

182
    /**
183
     * Gets a specified comments frame from the specified tag, trying to match the description and
184
     * language but accepting an incomplete match.
185
     * The method tries matching with the following order of precedence:
186
     * * The first frame with a matching description and language
187
     * * The first frame with a matching language
188
     * * The first frame with a matching description
189
     * * The first frame
190
     * @param frames Frames to search for best matching frame
191
     * @param description Description to match
192
     * @param language ISO-639-2 language code to match
193
     */
194
    public static findPreferred(frames: CommentsFrame[], description: string, language?: string): CommentsFrame {
195
        Guards.truthy(frames, "frames");
69✔
196

197
        // Original .NET comments:
198
        // This is weird, so bear with me. The best thing we can have is something straightforward
199
        // and in our own language. If it has a description, then it is probably used for something
200
        // other than an actual comment. If that doesn't work, we'd still rather have something in
201
        // our own language than something in another. After that, all we have left are things in
202
        // other languages, so we'd rather have one with actual content, so we try to get one with
203
        // no description first.
204

205
        const skipITunes = !description || !description.startsWith("iTun");
69✔
206

207
        let bestValue = -1;
69✔
208
        let bestFrame: CommentsFrame;
209

210
        for (const frame of frames) {
69✔
211
            if (skipITunes && frame.description.startsWith("iTun")) {
17!
212
                continue;
×
213
            }
214

215
            const sameName = frame.description === description;
17✔
216
            const sameLang = frame.language === language;
17✔
217

218
            if (sameName && sameLang) {
17✔
219
                return frame;
5✔
220
            }
221

222
            const value = sameLang ? 2 : sameName ? 1 : 0;
12✔
223

224
            if (value <= bestValue) {
12✔
225
                continue;
1✔
226
            }
227

228
            bestValue = value;
11✔
229
            bestFrame = frame;
11✔
230
        }
231

232
        return bestFrame;
64✔
233
    }
234

235
    /** @inheritDoc */
236
    public clone(): Frame {
237
        const frame = CommentsFrame.fromDescription(this._description, this._language, this._textEncoding);
1✔
238
        frame._text = this._text;
1✔
239
        return frame;
1✔
240
    }
241

242
    /**
243
     * Gets a string representation of the current instance.
244
     * @returns String with the comment text
245
     */
246
    public toString(): string {
247
        return this.text;
3✔
248
    }
249

250
    protected parseFields(data: ByteVector): void {
251
        if (data.length < 4) {
16✔
252
            throw new CorruptFileError("Not enough bytes in field");
2✔
253
        }
254

255
        this.textEncoding = data.get(0);
14✔
256
        this._language = data.subarray(1, 3).toString(StringType.Latin1);
14✔
257

258
        // Instead of splitting into two strings, in the format [{desc}\0{value}], try splitting
259
        // into three strings in case of a malformatted [{desc}\0{value}\0].
260
        const split = data.subarray(4).toStrings(this.textEncoding, 3);
14✔
261

262
        if (split.length === 0) {
14✔
263
            // No data in the frame.
264
            this._description = "";
2✔
265
            this._text = "";
2✔
266
        } else if (split.length === 1) {
12✔
267
            // Bad comment frame. Assume it lacks a description.
268
            this._description = "";
3✔
269
            this._text = split[0];
3✔
270
        } else {
271
            this._description = split[0];
9✔
272
            this._text = split[1];
9✔
273
        }
274
    }
275

276
    protected renderFields(version: number): ByteVector {
277
        const encoding = Frame.correctEncoding(this.textEncoding, version);
2✔
278
        return ByteVector.concatenate(
2✔
279
            encoding,
280
            ByteVector.fromString(this.language, StringType.Latin1),
281
            ByteVector.fromString(this.description, encoding),
282
            ByteVector.getTextDelimiter(encoding),
283
            ByteVector.fromString(this.text, encoding)
284
        );
285
    }
286
}
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