• 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

94.96
/src/asf/objects/metadataLibraryObject.ts
1
import BaseObject from "./baseObject";
1✔
2
import ReadWriteUtils from "../readWriteUtils";
1✔
3
import {ByteVector} from "../../byteVector";
1✔
4
import {Guids, ObjectType} from "../constants";
1✔
5
import {DataType, DescriptorBase, DescriptorValue} from "./descriptorBase";
1✔
6
import {CorruptFileError} from "../../errors";
1✔
7
import {File} from "../../file";
8
import {Guards} from "../../utils";
1✔
9

10
/**
11
 * This class provides a representation of an ASF description record to be used inside a
12
 * MetadataLibraryObject.
13
 * @remarks
14
 *     This class can store various types of information. Although {@link toString} provides
15
 *     a representation of all types of values, it is recommended to determine which of the `get*`
16
 *     methods to use by accessing {@link type}
17
 */
18
export class MetadataDescriptor extends DescriptorBase {
1✔
19
    private readonly _languageListIndex: number;
20
    private readonly _streamNumber: number;
21

22
    // #region Constructors
23

24
    /**
25
     * Constructs and initializes a new instance.
26
     * @param languageListIndex Index of the language
27
     * @param streamNumber Index of the stream
28
     * @param name Name of the metadata library object
29
     * @param type Datatype of the object
30
     * @param value Value to store in the instance
31
     */
32
    public constructor(
33
        languageListIndex: number,
34
        streamNumber: number,
35
        name: string,
36
        type: DataType,
37
        value: DescriptorValue
38
    ) {
39
        super(name, type, value);
87✔
40
        Guards.ushort(languageListIndex, "languageListIndex");
77✔
41
        Guards.ushort(streamNumber, "streamNumber");
77✔
42

43
        this._languageListIndex = languageListIndex;
77✔
44
        this._streamNumber = streamNumber;
77✔
45
    }
46

47
    /**
48
     * Instantiates a new instance by reading in the contents from a file.
49
     * @param file The file to read the raw ASF description record from
50
     * @internal
51
     */
52
    public static fromFile(file: File): MetadataDescriptor {
53
        Guards.truthy(file, "file");
22✔
54

55
        // Field name          Field type Size (bits)
56
        // Language List Index WORD       16
57
        // Stream Number       WORD       16
58
        // Name Length         WORD       16
59
        // Data Type           WORD       16
60
        // Data Length         DWORD      32
61
        // Name                WCHAR      varies
62
        // Data                See below  varies
63
        const languageListIndex = ReadWriteUtils.readWord(file);
22✔
64
        const streamNumber = ReadWriteUtils.readWord(file);
22✔
65
        const nameLength = ReadWriteUtils.readWord(file);
22✔
66
        const dataType = ReadWriteUtils.readWord(file);
22✔
67
        const dataLength = ReadWriteUtils.readDWord(file);
22✔
68
        const name = ReadWriteUtils.readUnicode(file, nameLength);
22✔
69

70
        let value: DescriptorValue;
71
        switch (dataType) {
22✔
72
            case DataType.Word:
22!
73
                value = ReadWriteUtils.readWord(file);
5✔
74
                break;
5✔
75
            case DataType.Bool:
76
                // NOTE: The ASF specification says metadata description objects should be 2 bytes
77
                //    however the original .NET implementation reads them as DWORDs. It might be a
78
                //    bug in the .NET implementation, or could be some apps read/write them as
79
                //    DWORDs. So, let's hedge our bets and try to read either.
80
                if (dataLength === 4) {
4✔
81
                    value = ReadWriteUtils.readDWord(file) > 0;
1✔
82
                } else {
83
                    value = ReadWriteUtils.readWord(file) > 0;
3✔
84
                }
85
                break;
4✔
86
            case DataType.DWord:
87
                value = ReadWriteUtils.readDWord(file);
1✔
88
                break;
1✔
89
            case DataType.QWord:
90
                value = ReadWriteUtils.readQWord(file);
1✔
91
                break;
1✔
92
            case DataType.Unicode:
93
                value = ReadWriteUtils.readUnicode(file, dataLength);
1✔
94
                break;
1✔
95
            case DataType.Bytes:
96
                value = file.readBlock(dataLength);
9✔
97
                break;
9✔
98
            case DataType.Guid:
99
                value = ReadWriteUtils.readGuid(file);
1✔
100
                break;
1✔
101
            default:
102
                throw new CorruptFileError("Failed to parse description record.");
×
103
        }
104

105
        return new MetadataDescriptor(languageListIndex, streamNumber, name, dataType, value);
22✔
106
    }
107

108
    // #endregion
109

110
    // #region Properties
111

112
    /**
113
     * Gets the index of the language associated with the current instance.
114
     */
115
    public get languageListIndex(): number { return this._languageListIndex; }
54✔
116

117
    /**
118
     * Gets the index of the stream associated with the current instance.
119
     */
120
    public get streamNumber(): number { return this._streamNumber; }
46✔
121

122
    // #endregion
123

124
    // #region Methods
125

126
    /** @inheritDoc */
127
    public render(): ByteVector {
128
        let value: ByteVector;
129
        switch (this.type) {
23✔
130
            case DataType.QWord:
23✔
131
                value = ReadWriteUtils.renderQWord(this.ulongValue);
1✔
132
                break;
1✔
133
            case DataType.DWord:
134
                value = ReadWriteUtils.renderDWord(this.uintValue);
1✔
135
                break;
1✔
136
            case DataType.Word:
137
                value = ReadWriteUtils.renderWord(this.ushortValue);
7✔
138
                break;
7✔
139
            case DataType.Bool:
140
                // NOTE: For whatever reason metadata content descriptions are WORDs.
141
                value = ReadWriteUtils.renderWord(this.boolValue ? 1 : 0);
3!
142
                break;
3✔
143
            case DataType.Unicode:
144
                value = ReadWriteUtils.renderUnicode(this.stringValue);
1✔
145
                break;
1✔
146
            case DataType.Bytes:
147
                value = this.byteValue;
9✔
148
                break;
9✔
149
            case DataType.Guid:
150
                value = this.guidValue.toBytes();
1✔
151
                break;
1✔
152
        }
153

154
        const nameBytes = ReadWriteUtils.renderUnicode(this.name);
23✔
155
        return ByteVector.concatenate(
23✔
156
            ReadWriteUtils.renderWord(this._languageListIndex),
157
            ReadWriteUtils.renderWord(this._streamNumber),
158
            ReadWriteUtils.renderWord(nameBytes.length),
159
            ReadWriteUtils.renderWord(this.type),
160
            ReadWriteUtils.renderDWord(value.length),
161
            nameBytes,
162
            value
163
        );
164
    }
165

166
    // #endregion
167
}
168

169
/**
170
 * This class provides a representation of an ASF metadata library object which can be read from
171
 * and written to disk.
172
 */
173
export class MetadataLibraryObject extends BaseObject {
1✔
174
    private readonly _records: MetadataDescriptor[] = [];
151✔
175

176
    // #region Constructors
177

178
    private constructor() {
179
        super();
151✔
180
    }
181

182
    /**
183
     * Constructs and initializes a new instance that does not contain any records.
184
     */
185
    public static fromEmpty(): MetadataLibraryObject {
186
        const instance = new MetadataLibraryObject();
129✔
187
        instance.initializeFromGuid(Guids.ASF_METADATA_LIBRARY_OBJECT);
129✔
188
        return instance;
129✔
189
    }
190

191
    /**
192
     * Constructs and initializes a new instance by reading the object from a file.
193
     * @param file File to read the instance from
194
     * @param position Offset into the file where the object begins
195
     */
196
    public static fromFile(file: File, position: number): MetadataLibraryObject {
197
        const instance = new MetadataLibraryObject();
22✔
198
        instance.initializeFromFile(file, position);
22✔
199

200
        if (!instance.guid.equals(Guids.ASF_METADATA_LIBRARY_OBJECT)) {
13!
201
            throw new CorruptFileError("Object GUID does not match expected metadata library object GUID");
×
202
        }
203
        if (instance.originalSize < 26) {
13!
204
            throw new CorruptFileError("Metadata library object is too small");
×
205
        }
206

207
        const count = ReadWriteUtils.readWord(file);
13✔
208
        for (let i = 0; i < count; i++) {
13✔
209
            instance._records.push(MetadataDescriptor.fromFile(file));
14✔
210
        }
211

212
        return instance;
13✔
213
    }
214

215
    // #endregion
216

217
    // #region Properties
218

219
    /**
220
     * Gets whether or not the current instance contains any records.
221
     * @returns
222
     *     `true` if the current instance does not contain any records, `false`
223
     *     otherwise.
224
     */
225
    public get isEmpty(): boolean { return this._records.length === 0; }
2✔
226

227
    /** @inheritDoc */
228
    public get objectType(): ObjectType { return ObjectType.MetadataLibraryObject; }
7✔
229

230
    /**
231
     * Gets all records stored in the current instance.
232
     */
233
    public get records(): MetadataDescriptor[] { return this._records; }
18✔
234

235
    // #endregion
236

237
    // #region Methods
238

239
    /**
240
     * Adds a record to the current instance.
241
     * @param record Record to add to the current instance
242
     */
243
    public addRecord(record: MetadataDescriptor): void {
244
        Guards.truthy(record, "record");
41✔
245
        this._records.push(record);
39✔
246
    }
247

248
    /**
249
     * Gets all records with a given language, stream, and any of a collection of names from the
250
     * current instance.
251
     * @param languageListIndex Index of the desired language in the language list
252
     * @param streamNumber Index of the stream in the file the desired records applies to
253
     * @param names List of names of the records to return
254
     */
255
    public getRecords(languageListIndex: number, streamNumber: number, ... names: string[]): MetadataDescriptor[] {
256
        Guards.ushort(languageListIndex, "languageListIndex");
20✔
257
        Guards.ushort(streamNumber, "streamNumber");
15✔
258
        Guards.truthy(names, "names");
10✔
259

260
        return this._records.filter((r) =>
10✔
261
            r.languageListIndex === languageListIndex &&
16✔
262
            r.streamNumber === streamNumber &&
263
            names.indexOf(r.name) >= 0
264
        );
265
    }
266

267
    /**
268
     * Removes all records with a given language, stream, and name from the current instance.
269
     * @param languageListIndex Language list index of the records to be removed
270
     * @param streamNumber Index of the stream in the file the desired records to remove
271
     * @param name Name of the records to remove
272
     */
273
    public removeRecords(languageListIndex: number, streamNumber: number, name: string): void {
274
        Guards.ushort(languageListIndex, "languageListIndex");
14✔
275
        Guards.ushort(streamNumber, "streamNumber");
9✔
276

277
        for (let i = this._records.length - 1; i >= 0; i--) {
4✔
278
            // Remove matching records
279
            const rec = this._records[i];
13✔
280
            if (rec.languageListIndex === languageListIndex &&
13✔
281
                rec.streamNumber === streamNumber &&
282
                rec.name === name
283
            ) {
284
                this._records.splice(i, 1);
4✔
285
            }
286
        }
287
    }
288

289
    /** @inheritDoc */
290
    public render(): ByteVector {
291
        const output = ByteVector.concatenate(
21✔
292
            ReadWriteUtils.renderWord(this._records.length),
293
            ... this._records.map((r) => r.render())
12✔
294
        );
295
        return super.renderInternal(output);
21✔
296
    }
297

298
    /**
299
     * Sets a collection of records for a given language, language, ane name, removing the existing
300
     * records that match.
301
     * @remarks
302
     *     All added entries in `records` should match the provided `languageListIndex`,
303
     *     `streamNumber`, and `name`, but this will not be verified by the method. The records
304
     *     will be added with their own values and not those provided in the method arguments. The
305
     *     arguments are only used for removing existing values and determining where to position
306
     *     the new records.
307
     * @param languageListIndex Index of the desired language in the language list
308
     * @param streamNumber Index of the stream in the file the desired records applies to
309
     * @param name Names of the records to remove
310
     * @param records Records to insert into the current instance
311
     */
312
    public setRecords(
313
        languageListIndex: number,
314
        streamNumber: number,
315
        name: string,
316
        ... records: MetadataDescriptor[]
317
    ): void {
318
        Guards.ushort(languageListIndex, "languageListIndex");
15✔
319
        Guards.ushort(streamNumber, "streamNumber");
10✔
320
        Guards.notNullOrUndefined(name, "name");
5✔
321

322
        let position = this._records.length;
3✔
323
        for (let i = this._records.length - 1; i >= 0; i--) {
3✔
324
            // Remove matching records
325
            const record = this._records[i];
9✔
326
            if (record.languageListIndex === languageListIndex &&
9✔
327
                record.streamNumber === streamNumber &&
328
                record.name === name
329
            ) {
330
                this._records.splice(i, 1);
2✔
331
                position = i;
2✔
332
            }
333
        }
334

335
        // Insert the new records
336
        this._records.splice(position, 0, ... records);
3✔
337
    }
338

339
    // #endregion
340
}
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