UNPKG

node-id3

Version:

Pure JavaScript ID3v2 Tag writer and reader

1,221 lines (1,098 loc) 51.3 kB
const NodeID3 = require('../index.js') const jsmediatags = require("jsmediatags") const assert = require('assert') const chai = require('chai') const expect = chai.expect const iconv = require('iconv-lite') const fs = require('fs') const ID3Util = require('../src/ID3Util') describe('NodeID3', function () { describe('#create()', function () { it('empty tags', function () { assert.strictEqual(NodeID3.create({}).compare(Buffer.from([0x49, 0x44, 0x33, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])), 0) }) it('text frames', function () { const tags = { TIT2: "abcdeÜ看板かんばん", album: "nasÖÄkdnasd", notfound: "notfound", year: 1990 } const buffer = NodeID3.create(tags) const titleSize = 10 + 1 + iconv.encode(tags.TIT2, 'utf16').length const albumSize = 10 + 1 + iconv.encode(tags.album, 'utf16').length const yearSize = 10 + 1 + iconv.encode(tags.year, 'utf16').length assert.strictEqual(buffer.length, 10 + // ID3 frame header titleSize + // TIT2 header + encoding byte + utf16 bytes + utf16 string albumSize +// same as above for album, yearSize ) // Check ID3 header assert.ok(buffer.includes( Buffer.concat([ Buffer.from([0x49, 0x44, 0x33, 0x03, 0x00, 0x00]), Buffer.from(ID3Util.encodeSize(titleSize + albumSize + yearSize)) ]) )) // Check TIT2 frame assert.ok(buffer.includes( Buffer.concat([ Buffer.from([0x54, 0x49, 0x54, 0x32]), sizeToBuffer(titleSize - 10), Buffer.from([0x00, 0x00]), Buffer.from([0x01]), iconv.encode(tags.TIT2, 'utf16') ]) )) // Check album frame assert.ok(buffer.includes( Buffer.concat([ Buffer.from([0x54, 0x41, 0x4C, 0x42]), sizeToBuffer(albumSize - 10), Buffer.from([0x00, 0x00]), Buffer.from([0x01]), iconv.encode(tags.album, 'utf16') ]) )) assert.ok(buffer.includes( Buffer.concat([ Buffer.from([0x54, 0x59, 0x45, 0x52]), sizeToBuffer(yearSize - 10), Buffer.from([0x00, 0x00]), Buffer.from([0x01]), iconv.encode(tags.year, 'utf16') ]) )) }) it('user defined text frames', function() { let tags = { userDefinedText: { description: "abc", value: "defg" } } let buffer = NodeID3.create(tags).slice(10) const descEncoded = iconv.encode(tags.userDefinedText.description + "\0", "UTF-16") const valueEncoded = iconv.encode(tags.userDefinedText.value, "UTF-16") assert.strictEqual(Buffer.compare( buffer, Buffer.concat([ Buffer.from([0x54, 0x58, 0x58, 0x58]), sizeToBuffer(1 + descEncoded.length + valueEncoded.length), Buffer.from([0x00, 0x00]), Buffer.from([0x01]), descEncoded, valueEncoded ]) ), 0) tags = { userDefinedText: [{ description: "abc", value: "defg" }, { description: "hij", value: "klmn" }] } buffer = NodeID3.create(tags).slice(10) const desc1Encoded = iconv.encode(tags.userDefinedText[0].description + "\0", "UTF-16") const value1Encoded = iconv.encode(tags.userDefinedText[0].value, "UTF-16") const desc2Encoded = iconv.encode(tags.userDefinedText[1].description + "\0", "UTF-16") const value2Encoded = iconv.encode(tags.userDefinedText[1].value, "UTF-16") assert.strictEqual(Buffer.compare( buffer, Buffer.concat([ Buffer.from([0x54, 0x58, 0x58, 0x58]), sizeToBuffer(1 + desc1Encoded.length + value1Encoded.length), Buffer.from([0x00, 0x00]), Buffer.from([0x01]), desc1Encoded, value1Encoded, Buffer.from([0x54, 0x58, 0x58, 0x58]), sizeToBuffer(1 + desc2Encoded.length + value2Encoded.length), Buffer.from([0x00, 0x00]), Buffer.from([0x01]), desc2Encoded, value2Encoded ]) ), 0) }) it('create APIC frame', function() { const tags = { image: { description: "asdf", imageBuffer: Buffer.from('5B307836312C20307836322C20307836332C20307836345D', 'hex'), mime: "image/jpeg", type: { id: NodeID3.TagConstants.AttachedPicture.FRONT_COVER } } } assert.strictEqual(Buffer.compare( NodeID3.create(tags), Buffer.from('4944330300000000003B4150494300000031000001696D6167652F6A7065670003FFFE610073006400660000005B307836312C20307836322C20307836332C20307836345D', 'hex') ), 0) assert.strictEqual(Buffer.compare( NodeID3.create({ image: __dirname + '/smallimg' }), NodeID3.create({ image: { imageBuffer: fs.readFileSync(__dirname + '/smallimg') } }) ), 0) assert.strictEqual(Buffer.compare( NodeID3.create({ image: fs.readFileSync(__dirname + '/smallimg') }), NodeID3.create({ image: { imageBuffer: fs.readFileSync(__dirname + '/smallimg') } }) ), 0) // iTunes fix if description is empty assert.strictEqual(NodeID3.create({ image: fs.readFileSync(__dirname + '/smallimg') })[20], 0x00) }) it('create USLT frame', function() { const tags = { unsynchronisedLyrics: { language: "deu", shortText: "Haiwsää#", text: "askdh ashd olahs elowz dlouaish dkajh" } } assert.strictEqual(Buffer.compare( NodeID3.create(tags), Buffer.from('4944330300000000006E55534C5400000064000001646575FFFE48006100690077007300E400E40023000000FFFE610073006B00640068002000610073006800640020006F006C00610068007300200065006C006F0077007A00200064006C006F0075006100690073006800200064006B0061006A006800', 'hex') ), 0) }) it('create SYLT frame', function() { const TagConstants = NodeID3.TagConstants const tags = { synchronisedLyrics: [{ language: "deu", timeStampFormat: TagConstants.TimeStampFormat.MILLISECONDS, contentType: TagConstants.SynchronisedLyrics.ContentType.LYRICS, shortText: "Haiwsää#", synchronisedText: [{ text: "askdh ashd olahs", timeStamp: 0 }, { text: "elowz dlouaish dkajh", timeStamp: 1000 }] }] } assert.strictEqual(Buffer.compare( NodeID3.create(tags), Buffer.from('4944330300000000007c53594c54000000720000016465750201fffe48006100690077007300e400e40023000000fffe610073006b00640068002000610073006800640020006f006c00610068007300000000000000fffe65006c006f0077007a00200064006c006f0075006100690073006800200064006b0061006a0068000000000003e8', 'hex') ), 0) }) it('create COMM frame', function() { const tags = { comment: { language: "deu", shortText: "Haiwsää#", text: "askdh ashd olahs elowz dlouaish dkajh" } } const frameBuf = Buffer.from('4944330300000000006E434F4D4D00000064000001646575FFFE48006100690077007300E400E40023000000FFFE610073006B00640068002000610073006800640020006F006C00610068007300200065006C006F0077007A00200064006C006F0075006100690073006800200064006B0061006A006800', 'hex') assert.strictEqual(Buffer.compare( NodeID3.create(tags), frameBuf ), 0) }) it('create POPM frame', function() { const frameBuf = Buffer.from('49443303000000000020504F504D0000001600006D61696C406578616D706C652E636F6D00C00000000C', 'hex') const tags = { popularimeter: { email: "mail@example.com", rating: 192, // 1-255 counter: 12 } } assert.strictEqual(Buffer.compare( NodeID3.create(tags), frameBuf ), 0) }) it('create PRIV frame', function() { const frameBuf = Buffer.from('4944330300000000003250524956000000140000416243006173646F61687764696F686177646177505249560000000A000041624353535300010205', 'hex') const tags = { PRIV: [{ ownerIdentifier: "AbC", data: Buffer.from("asdoahwdiohawdaw") }, { ownerIdentifier: "AbCSSS", data: Buffer.from([0x01, 0x02, 0x05]) }] } assert.deepStrictEqual( NodeID3.create(tags), frameBuf ) }) it("create UFID frame", function () { const frameBuf = Buffer.from('49443303000000000042554649440000001700006f776e65722d69642d31006964656e7469666965722d31554649440000001700006f776e65722d69642d32006964656e7469666965722d32', 'hex') const tags = { UFID: [{ ownerIdentifier: "owner-id-1", identifier: Buffer.from("identifier-1") }, { ownerIdentifier: "owner-id-2", identifier: Buffer.from("identifier-2") }], } assert.deepStrictEqual(NodeID3.create(tags), frameBuf) }) it('create CHAP frame', function() { const frameBuf = Buffer.from('494433030000000000534348415000000049000048657921000000138800001F400000007B000001C8544954320000000F000001FFFE6100620063006400650066005450453100000011000001FFFE61006B0073006800640061007300', 'hex') const tags = { CHAP: [{ elementID: "Hey!", //THIS MUST BE UNIQUE! startTimeMs: 5000, endTimeMs: 8000, startOffsetBytes: 123, // OPTIONAL! endOffsetBytes: 456, // OPTIONAL! tags: { // OPTIONAL title: "abcdef", artist: "akshdas" } }]} assert.deepStrictEqual( NodeID3.create(tags), frameBuf ) }) it('create WXXX frame', function() { const frameBuf = Buffer.from('4944330300000000002c5758585800000022000001fffe61006200630064002300000068747470733a2f2f6578616d706c652e636f6d', 'hex') const tags = { userDefinedUrl: [{ description: 'abcd#', url: 'https://example.com' }] } assert.deepStrictEqual( NodeID3.create(tags), frameBuf ) }) it('create URL frame', function() { const frameBuf = Buffer.from('4944330300000000003b57434f4d00000013000068747470733a2f2f6578616d706c652e636f6d574f414600000014000068747470733a2f2f6578616d706c65322e636f6d', 'hex') const tags = { commercialUrl: ['https://example.com'], fileUrl: 'https://example2.com' } assert.deepStrictEqual( NodeID3.create(tags), frameBuf ) }) it('create ETCO frame', function() { const frameBuf = Buffer.from('494433030000000000154554434F0000000B00000101000003E80500000539', 'hex') const tags = { eventTimingCodes: { timeStampFormat: NodeID3.TagConstants.TimeStampFormat.MPEG_FRAMES, keyEvents: [ { type: NodeID3.TagConstants.EventTimingCodes.EventType.END_OF_INITIAL_SILENCE, timeStamp: 1000 }, { type: NodeID3.TagConstants.EventTimingCodes.EventType.OUTRO_END, timeStamp: 1337 } ] } } assert.deepStrictEqual( NodeID3.create(tags), frameBuf ) }) it('create GEOB frame', function() { const frameBuf = Buffer.from('4944330300000000006147454f42000000570000016170706c69636174696f6e2f6f637465742d73747265616d00fffe660069006c0065006e0061006d0065000000fffe6400650073006300720069007000740069006f006e000000131313131313131313131313131313', 'hex') const tags = { generalObject: { mimeType: 'application/octet-stream', filename: 'filename', contentDescription: 'description', encapsulatedObject: Buffer.alloc(15, 0x13), } } assert.deepStrictEqual( NodeID3.create(tags), frameBuf ) }) it('create GEOB frames', function() { const framesBuf = Buffer.from('4944330300000000014647454f42000000570000016170706c69636174696f6e2f6f637465742d73747265616d00fffe660069006c0065006e0061006d0065000000fffe6400650073006300720069007000740069006f006e00000013131313131313131313131313131347454f420000005b0000016170706c69636174696f6e2f6f637465742d73747265616d00fffe660069006c0065006e0061006d00650032000000fffe6400650073006300720069007000740069006f006e0032000000141414141414141414141414141414', 'hex') const tags = { generalObject: [{ mimeType: 'application/octet-stream', filename: 'filename', contentDescription: 'description', encapsulatedObject: Buffer.alloc(15, 0x13), }, { mimeType: 'application/octet-stream', filename: 'filename2', contentDescription: 'description2', encapsulatedObject: Buffer.alloc(15, 0x14), }] } assert.deepStrictEqual( NodeID3.create(tags), framesBuf ) }) it('create COMR frame', function() { const frameBufRandomImage = Buffer.from('49443303000000000076434f4d520000006c00000145555231352f444b4b31372e39323200303939393039303168747470733a2f2f6578616d706c652e636f6d0005fffe53006f006d0065006f006e0065000000fffe53006f006d0065007400680069006e0067000000696d6167652f00131313131313131313131313131313', 'hex') const tags = { commercialFrame: { prices: { 'EUR': 15, 'DKK': 17.922 }, validUntil: { year: 999, month: 9, day: 1}, contactUrl: 'https://example.com', receivedAs: NodeID3.TagConstants.CommercialFrame.ReceivedAs.AS_NOTE_SHEETS, nameOfSeller: 'Someone', description: 'Something', sellerLogo: { picture: Buffer.alloc(15, 0x13) } } } assert.deepStrictEqual( NodeID3.create(tags), frameBufRandomImage ) tags.commercialFrame.sellerLogo.picture = __dirname + '/smallimg' const resultWithPictureString = NodeID3.create(tags) tags.commercialFrame.sellerLogo.picture = fs.readFileSync(__dirname + '/smallimg') const resultWithPictureBuffer = NodeID3.create(tags) assert.deepStrictEqual(resultWithPictureBuffer, resultWithPictureString) delete tags.commercialFrame.sellerLogo const frameBufNoImage = Buffer.from('49443303000000000060434f4d520000005600000145555231352f444b4b31372e39323200303939393039303168747470733a2f2f6578616d706c652e636f6d0005fffe53006f006d0065006f006e0065000000fffe53006f006d0065007400680069006e0067000000', 'hex') assert.deepStrictEqual( NodeID3.create(tags), frameBufNoImage ) }) it('create mixed v3/v4 tag', function() { const frameBuf = Buffer.from('494433030000000000315449543200000009000001fffe61006c006c005459455200000005000001fffe33005444524300000005000001fffe3400', 'hex') const tags = { title: "all", year: 3, recordingTime: 4 } assert.deepStrictEqual( NodeID3.create(tags), frameBuf ) }) }) describe('#write()', function() { const nonExistingFilepath = './hopefully-does-not-exist.mp3' it('sync not existing filepath', function() { chai.assert.isFalse(fs.existsSync(nonExistingFilepath)) chai.assert.instanceOf( NodeID3.write({}, nonExistingFilepath), Error ) }) it('async not existing filepath', function() { chai.assert.isFalse(fs.existsSync(nonExistingFilepath)) NodeID3.write({}, nonExistingFilepath, function(err) { if(!(err instanceof Error)) { assert.fail("No error thrown on non-existing filepath") } }) }) const buffer = Buffer.from([0x02, 0x06, 0x12, 0x22]) let tags = {title: "abc"} const filepath = './testfile.mp3' it('sync write file without id3 tag', function() { fs.writeFileSync(filepath, buffer, 'binary') NodeID3.write(tags, filepath) const newFileBuffer = fs.readFileSync(filepath) fs.unlinkSync(filepath) assert.strictEqual(Buffer.compare( newFileBuffer, Buffer.concat([NodeID3.create(tags), buffer]) ), 0) }) it('async write file without id3 tag', function(done) { fs.writeFileSync(filepath, buffer, 'binary') NodeID3.write(tags, filepath, function() { const newFileBuffer = fs.readFileSync(filepath) fs.unlinkSync(filepath) if(Buffer.compare( newFileBuffer, Buffer.concat([NodeID3.create(tags), buffer]) ) === 0) { done() } else { done(new Error("buffer not the same")) } }) }) const bufferWithTag = Buffer.concat([NodeID3.create(tags), buffer]) tags = {album: "ix123"} it('sync write file with id3 tag', function() { fs.writeFileSync(filepath, bufferWithTag, 'binary') NodeID3.write(tags, filepath) const newFileBuffer = fs.readFileSync(filepath) fs.unlinkSync(filepath) assert.strictEqual(Buffer.compare( newFileBuffer, Buffer.concat([NodeID3.create(tags), buffer]) ), 0) }) it('async write file with id3 tag', function(done) { fs.writeFileSync(filepath, bufferWithTag, 'binary') NodeID3.write(tags, filepath, function() { const newFileBuffer = fs.readFileSync(filepath) fs.unlinkSync(filepath) if(Buffer.compare( newFileBuffer, Buffer.concat([NodeID3.create(tags), buffer]) ) === 0) { done() } else { done(new Error("file written incorrectly")) } }) }) }) describe('#read()', function() { it('read empty id3 tag', function() { const frame = NodeID3.create({}) assert.deepStrictEqual( NodeID3.read(frame), {raw: {}} ) }) it('read text frames id3 tag', function() { const frame = NodeID3.create({ title: "asdfghjÄÖP", album: "naBGZwssg" }) assert.deepStrictEqual( NodeID3.read(frame), { title: "asdfghjÄÖP", album: "naBGZwssg", raw: { TIT2: "asdfghjÄÖP", TALB: "naBGZwssg" }} ) }) it('read tag with broken frame', function() { const frame = NodeID3.create({ title: "asdfghjÄÖP", album: "naBGZwssg" }) frame[10] = 0x99 assert.deepStrictEqual( NodeID3.read(frame), { album: "naBGZwssg", raw: { TALB: "naBGZwssg" }} ) }) it('read tag with bigger size', function() { const frame = NodeID3.create({ title: "asdfghjÄÖP", album: "naBGZwssg" }) const newFrameSize = 127 frame[9] = 127 assert.ok(frame.length < newFrameSize + 10) assert.deepStrictEqual( NodeID3.read(frame), { title: "asdfghjÄÖP", album: "naBGZwssg", raw: { TIT2: "asdfghjÄÖP", TALB: "naBGZwssg" }} ) }) it('read tag with smaller size', function() { const frame = NodeID3.create({ title: "asdfghjÄÖP", album: "naBGZwssg" }) frame[9] -= 25 assert.deepStrictEqual( NodeID3.read(frame), { title: "asdfghjÄÖP", raw: { TIT2: "asdfghjÄÖP" }} ) }) it('read tag with invalid size', function() { const frame = NodeID3.create({ title: 'a' }) frame[9] = 128 assert.deepStrictEqual( NodeID3.read(frame).raw, {} ) }) it('read TXXX frame', function() { const tags = { userDefinedText: {description: "abc", value: "deg"} } const frame = NodeID3.create(tags) assert.deepStrictEqual( NodeID3.read(frame), { userDefinedText: [tags.userDefinedText], raw: { TXXX: [tags.userDefinedText] } } ) }) it('read TXXX array frame', function() { const tags = { userDefinedText: [{description: "abc", value: "deg"}, {description: "abcd", value: "efgh"}] } const frame = NodeID3.create(tags) assert.deepStrictEqual( NodeID3.read(frame), { userDefinedText: tags.userDefinedText, raw: { TXXX: tags.userDefinedText } } ) }) it('read APIC frame', function() { const withAll = Buffer.from("4944330300000000101C4150494300000016000000696D6167652F6A7065670003617364660061626364", "hex") const noDesc = Buffer.from("494433030000000000264150494300000012000000696D6167652F6A70656700030061626364", "hex") const obj = { description: "asdf", imageBuffer: Buffer.from([0x61, 0x62, 0x63, 0x64]), mime: "image/jpeg", type: { id: 3, name: "front cover" } } assert.deepStrictEqual( NodeID3.read(withAll).image, obj ) obj.description = undefined assert.deepStrictEqual( NodeID3.read(noDesc).image, obj ) }) it('read USLT frame', function() { const frameBuf = Buffer.from('4944330300000000006E55534C5400000064000001646575FFFE48006100690077007300E400E40023000000FFFE610073006B00640068002000610073006800640020006F006C00610068007300200065006C006F0077007A00200064006C006F0075006100690073006800200064006B0061006A006800', 'hex') const unsynchronisedLyrics = { language: "deu", shortText: "Haiwsää#", text: "askdh ashd olahs elowz dlouaish dkajh" } assert.deepStrictEqual( NodeID3.read(frameBuf).unsynchronisedLyrics, unsynchronisedLyrics ) }) it('read SYLT frame', function() { const frameBuf = Buffer.from('4944330300000000007c53594c54000000720000016465750201fffe48006100690077007300e400e40023000000fffe610073006b00640068002000610073006800640020006f006c00610068007300000000000000fffe65006c006f0077007a00200064006c006f0075006100690073006800200064006b0061006a0068000000000003e8', 'hex') const TagConstants = NodeID3.TagConstants const synchronisedLyrics = [{ language: "deu", timeStampFormat: TagConstants.TimeStampFormat.MILLISECONDS, contentType: TagConstants.SynchronisedLyrics.ContentType.LYRICS, shortText: "Haiwsää#", synchronisedText: [{ text: "askdh ashd olahs", timeStamp: 0 }, { text: "elowz dlouaish dkajh", timeStamp: 1000 }] }] assert.deepStrictEqual( NodeID3.read(frameBuf).synchronisedLyrics, synchronisedLyrics ) }) it('read COMM frame', function() { const frameBuf = Buffer.from('4944330300000000006E434F4D4D00000064000001646575FFFE48006100690077007300E400E40023000000FFFE610073006B00640068002000610073006800640020006F006C00610068007300200065006C006F0077007A00200064006C006F0075006100690073006800200064006B0061006A006800', 'hex') const comment = { language: "deu", shortText: "Haiwsää#", text: "askdh ashd olahs elowz dlouaish dkajh" } assert.deepStrictEqual( NodeID3.read(frameBuf).comment, comment ) }) it('read POPM frame', function() { const frameBuf = Buffer.from('49443303000000000020504F504D0000001600006D61696C406578616D706C652E636F6D00C00000000C', 'hex') const popularimeter = { email: "mail@example.com", rating: 192, // 1-255 counter: 12 } assert.deepStrictEqual( NodeID3.read(frameBuf).popularimeter, popularimeter ) }) it('read PRIV frame', function() { const frameBuf = Buffer.from('4944330300000000003250524956000000140000416243006173646F61687764696F686177646177505249560000000A000041624353535300010205', 'hex') const priv = [{ ownerIdentifier: "AbC", data: Buffer.from("asdoahwdiohawdaw") }, { ownerIdentifier: "AbCSSS", data: Buffer.from([0x01, 0x02, 0x05]) }] assert.deepStrictEqual( NodeID3.read(frameBuf).private, priv ) }) it("read UFID frame", function () { const frameBuf = Buffer.from('49443303000000000042554649440000001700006f776e65722d69642d31006964656e7469666965722d31554649440000001700006f776e65722d69642d32006964656e7469666965722d32', 'hex') const ufid = [{ ownerIdentifier: "owner-id-1", identifier: Buffer.from("identifier-1") }, { ownerIdentifier: "owner-id-2", identifier: Buffer.from("identifier-2") }] assert.deepStrictEqual( NodeID3.read(frameBuf).uniqueFileIdentifier, ufid ) }) it('read CHAP frame', function() { const frameBuf = Buffer.from('494433030000000000534348415000000049000048657921000000138800001F400000007B000001C8544954320000000F000001FFFE6100620063006400650066005450453100000011000001FFFE61006B0073006800640061007300', 'hex') const chap = [{ elementID: "Hey!", //THIS MUST BE UNIQUE! startTimeMs: 5000, endTimeMs: 8000, startOffsetBytes: 123, // OPTIONAL! endOffsetBytes: 456, // OPTIONAL! tags: { // OPTIONAL title: "abcdef", artist: "akshdas", raw: { TIT2: "abcdef", TPE1: "akshdas" } } }] assert.deepStrictEqual( NodeID3.read(frameBuf).chapter, chap ) }) it('read WXXX frame', function() { const frameBuf = Buffer.from('4944330300000000002c5758585800000022000001fffe61006200630064002300000068747470733a2f2f6578616d706c652e636f6d', 'hex') const userDefinedUrl = [{ description: 'abcd#', url: 'https://example.com' }] assert.deepStrictEqual( NodeID3.read(frameBuf).userDefinedUrl, userDefinedUrl ) }) it('read URL frame', function() { const frameBuf = Buffer.from('4944330300000000003d57434f4d0000001400000068747470733a2f2f6578616d706c652e636f6d574f41460000001500000068747470733a2f2f6578616d706c65322e636f6d', 'hex') const commercialUrl = ['https://example.com'] const fileUrl = 'https://example2.com' assert.deepStrictEqual( NodeID3.read(frameBuf).commercialUrl, commercialUrl ) assert.deepStrictEqual( NodeID3.read(frameBuf).fileUrl, fileUrl ) }) it('read ETCO frame', function() { const frameBuf = Buffer.from('494433030000000000154554434F0000000B00000101000003E80500000539', 'hex') const eventTimingCodes = { timeStampFormat: 0x01, keyEvents: [ { type: 0x01, timeStamp: 1000 }, { type: 0x05, timeStamp: 1337 } ] } assert.deepStrictEqual( NodeID3.read(frameBuf).eventTimingCodes, eventTimingCodes ) }) it('read COMR frame', function() { const frameBufRandomImage = Buffer.from('49443303000000000076434f4d520000006c00000145555231352f444b4b31372e39323200303939393039303168747470733a2f2f6578616d706c652e636f6d0005fffe53006f006d0065006f006e0065000000fffe53006f006d0065007400680069006e0067000000696d6167652f00131313131313131313131313131313', 'hex') const tags = { commercialFrame: { prices: { 'EUR': 15, 'DKK': 17.922 }, validUntil: { year: 999, month: 9, day: 1}, contactUrl: 'https://example.com', receivedAs: 0x05, nameOfSeller: 'Someone', description: 'Something', sellerLogo: { picture: Buffer.alloc(15, 0x13) } } } tags.commercialFrame.sellerLogo.mimeType = 'image/' tags.commercialFrame.prices['EUR'] = tags.commercialFrame.prices['EUR'].toString() tags.commercialFrame.prices['DKK'] = tags.commercialFrame.prices['DKK'].toString() assert.deepStrictEqual( NodeID3.read(frameBufRandomImage).commercialFrame[0], tags.commercialFrame ) delete tags.commercialFrame.sellerLogo const frameBufNoImage = Buffer.from('49443303000000000060434f4d520000005600000145555231352f444b4b31372e39323200303939393039303168747470733a2f2f6578616d706c652e636f6d0005fffe53006f006d0065006f006e0065000000fffe53006f006d0065007400680069006e0067000000', 'hex') assert.deepStrictEqual( NodeID3.read(frameBufNoImage).commercialFrame[0], tags.commercialFrame ) }) it('read GEOB frame', function() { const frameGeneralObject = Buffer.from('4944330300000000006147454f42000000570000016170706c69636174696f6e2f6f637465742d73747265616d00fffe660069006c0065006e0061006d0065000000fffe6400650073006300720069007000740069006f006e000000131313131313131313131313131313', 'hex') const tags = { generalObject: { mimeType: 'application/octet-stream', filename: 'filename', contentDescription: 'description', encapsulatedObject: Buffer.alloc(15, 0x13), } } // Ensure we are reading the correct frame const generalObject = NodeID3.read(frameGeneralObject).generalObject[0] // Assert the values match exactly assert.deepStrictEqual(generalObject.description, tags.generalObject.description) assert.deepStrictEqual(generalObject.filename, tags.generalObject.filename) assert.deepStrictEqual(generalObject.encapsulatedObject, tags.generalObject.encapsulatedObject) assert.deepStrictEqual(generalObject.mimeType, tags.generalObject.mimeType) }) it('read GEOB frames', function() { const frameGeneralObject = Buffer.from('4944330300000000014647454f42000000570000016170706c69636174696f6e2f6f637465742d73747265616d00fffe660069006c0065006e0061006d0065000000fffe6400650073006300720069007000740069006f006e00000013131313131313131313131313131347454f420000005b0000016170706c69636174696f6e2f6f637465742d73747265616d00fffe660069006c0065006e0061006d00650032000000fffe6400650073006300720069007000740069006f006e0032000000141414141414141414141414141414', 'hex') const tags = { generalObject: [{ mimeType: 'application/octet-stream', filename: 'filename', contentDescription: 'description', encapsulatedObject: Buffer.alloc(15, 0x13), }, { mimeType: 'application/octet-stream', filename: 'filename2', contentDescription: 'description2', encapsulatedObject: Buffer.alloc(15, 0x14), }] } // Assert the values match exactly assert.deepStrictEqual(NodeID3.read(frameGeneralObject).generalObject, tags.generalObject) }) it('create mixed v3/v4 tag', function() { const frameBuf = Buffer.from('494433030000000000315449543200000009000001fffe61006c006c005459455200000005000001fffe33005444524300000005000001fffe3400', 'hex') const tags = { title: "all", year: "3", recordingTime: "4" } assert.deepStrictEqual( NodeID3.read(frameBuf, { noRaw: true }), tags ) }) it('read exclude', function() { const tags = { TIT2: "abcdeÜ看板かんばん", album: "nasÖÄkdnasd", year: "1990" } const buffer = NodeID3.create(tags) const read = NodeID3.read(buffer, { exclude: ['TIT2'] }) delete read.raw delete tags.TIT2 assert.deepStrictEqual( read, tags ) }) it('read include', function() { const tags = { title: "abcdeÜ看板かんばん", album: "nasÖÄkdnasd", year: "1990" } const buffer = NodeID3.create(tags) const read = NodeID3.read(buffer, { include: ['TALB', 'TIT2'] }) delete read.raw delete tags.year assert.deepStrictEqual( read, tags ) }) it('onlyRaw', function() { const tags = { TIT2: "abcdeÜ看板かんばん", TALB: "nasÖÄkdnasd" } const buffer = NodeID3.create(tags) const read = NodeID3.read(buffer, { onlyRaw: true }) assert.deepStrictEqual( read, tags ) }) it('noRaw', function() { const tags = { title: "abcdeÜ看板かんばん", album: "nasÖÄkdnasd" } const buffer = NodeID3.create(tags) const read = NodeID3.read(buffer, { noRaw: true }) assert.deepStrictEqual( read, tags ) }) it('compressed frame', function() { const frameBufV3 = Buffer.from('4944330300000000001c5449543200000011008000000005789c6328492d2e0100045e01c1', 'hex') const frameBufV4 = Buffer.from('4944330400000000001c5449543200000011000900000005789c6328492d2e0100045e01c1', 'hex') const tags = { TIT2: 'test' } assert.deepStrictEqual( NodeID3.read(frameBufV3).raw, tags ) assert.deepStrictEqual( NodeID3.read(frameBufV4).raw, tags ) }) }) }) describe('ID3 helper functions', function () { describe('#removeTagsFromBuffer()', function () { it('no tags in buffer', function () { const emptyBuffer = Buffer.from([0x12, 0x04, 0x05, 0x01, 0x76, 0x27, 0x76, 0x27, 0x76, 0x27, 0x76, 0x27]) assert.strictEqual(Buffer.compare( emptyBuffer, NodeID3.removeTagsFromBuffer(emptyBuffer) ), 0) }) it('tags at start', function () { const buffer = Buffer.from([0x22, 0x73, 0x72]) const bufferWithID3 = Buffer.concat([ NodeID3.create({title: "abc"}), buffer ]) assert.strictEqual(Buffer.compare( NodeID3.removeTagsFromBuffer(bufferWithID3), buffer ), 0) }) it('tags in middle/end', function () { const buffer = Buffer.from([0x22, 0x73, 0x72]) const bufferWithID3 = Buffer.concat([ buffer, NodeID3.create({title: "abc"}), buffer ]) assert.strictEqual(Buffer.compare( NodeID3.removeTagsFromBuffer(bufferWithID3), Buffer.concat([buffer, buffer]) ), 0) }) }) }) const nodeTagsFull = { title: 'abc', album: '人物asfjas as das \\', comment: { language: 'en3', shortText: 'asd物f', text: 1337 }, unsynchronisedLyrics: { language: 'e33', shortText: 'asd物f', text: 'asd物f asd物f asd物f' }, userDefinedText: [ { description: "txxx name物", value: "TXXX value text" }, { description: "txxx name 2", value: "TXXX value text 2" } ], image: { mime: "jpeg", description: 'asd物f asd物f asd物f', imageBuffer: Buffer.from([0x02, 0x27, 0x17, 0x99]) }, popularimeter: { email: 'test@example.com', rating: 192, counter: 12 }, private: [{ ownerIdentifier: "AbC", data: "asdoahwdiohawdaw" }, { ownerIdentifier: "AbCSSS", data: Buffer.from([0x01, 0x02, 0x05]) }], chapter: [{ elementID: "Hey!", // THIS MUST BE UNIQUE! startTimeMs: 5000, endTimeMs: 8000, startOffsetBytes: 123, // OPTIONAL! endOffsetBytes: 456, // OPTIONAL! tags: { // OPTIONAL title: "abcdef", artist: "akshdas" } }], tableOfContents: [{ elementID: "toc1", // THIS MUST BE UNIQUE! isOrdered: false, // OPTIONAL, tells a player etc. if elements are in a specific order elements: ['chap1'], // OPTIONAL but most likely needed, contains the chapter/tableOfContents elementIDs tags: { // OPTIONAL title: "abcdef" } }], commercialUrl: ["commercialurl.com"], userDefinedUrl: [{ description: "URL description物", url: "https://example.com/" }] } const nodeTagsMissingValues = { comment: { language: 'en3', text: 1337 }, userDefinedText: [ { value: "TXXX value text" }, { value: "TXXX value text 2" } ], image: { mime: "jpeg", imageBuffer: Buffer.from([0x02, 0x27, 0x17, 0x99]) }, popularimeter: { email: 'test@example.com', counter: 12 }, private: [{ data: "asdoahwdiohawdaw" }, { data: Buffer.from([0x01, 0x02, 0x05]) }], chapter: [{ elementID: "Hey!", // THIS MUST BE UNIQUE! startTimeMs: 5000, endTimeMs: 8000 }], tableOfContents: [{ elementID: "toc1", // THIS MUST BE UNIQUE! elements: ['chap1'] }], } describe('Cross tests jsmediatags', function() { it('write full', function() { jsmediatags.read(NodeID3.create(nodeTagsFull), { onSuccess: (tag) => { const tags = tag.tags assert.strictEqual(tags.TIT2.data, nodeTagsFull.title) assert.strictEqual(tags.TALB.data, nodeTagsFull.album) assert.deepStrictEqual({ language: tags.COMM.data.language, shortText: tags.COMM.data.short_description, text: parseInt(tags.COMM.data.text) }, nodeTagsFull.comment) assert.deepStrictEqual({ language: tags.USLT.data.language, shortText: tags.USLT.data.descriptor, text: tags.USLT.data.lyrics }, nodeTagsFull.unsynchronisedLyrics) expect(tags.TXXX.map((t) => { return { description: t.data.user_description, value: t.data.data } })).to.have.deep.members(nodeTagsFull.userDefinedText) assert.deepStrictEqual({ mime: tags.APIC.data.format, description: tags.APIC.data.description, imageBuffer: Buffer.from(tags.APIC.data.data) }, nodeTagsFull.image) /* POPM seems broken in jsmediatags, data is null but tag looks correct */ /* PRIV seems broken in jsmediatags, data is null but tag looks correct */ assert.deepStrictEqual({ elementID: nodeTagsFull.chapter[0].elementID, startTimeMs: tags.CHAP.data.startTime, endTimeMs: tags.CHAP.data.endTime, startOffsetBytes: tags.CHAP.data.startOffset, endOffsetBytes: tags.CHAP.data.endOffset, tags: { title: tags.CHAP.data.subFrames.TIT2.data, artist: tags.CHAP.data.subFrames.TPE1.data } }, nodeTagsFull.chapter[0]) assert.deepStrictEqual({ elementID:nodeTagsFull.tableOfContents[0].elementID, isOrdered: false, elements: tags.CTOC.data.childElementIds, tags: { title: tags.CTOC.data.subFrames.TIT2.data } }, nodeTagsFull.tableOfContents[0]) assert.strictEqual(tags.WCOM.data, nodeTagsFull.commercialUrl[0]) assert.deepStrictEqual({ description: tags.WXXX.data.user_description, url: nodeTagsFull.userDefinedUrl[0].url /* The URL is always encoded with ISO-8859-1 => jsmediatags reads as UTF-16, can't use here*/ }, nodeTagsFull.userDefinedUrl[0]) }, onError: function(error) { throw error } }) }) it('write with missing values', function() { jsmediatags.read(NodeID3.create(nodeTagsMissingValues), { onSuccess: (tag) => { const tags = tag.tags assert.deepStrictEqual({ language: tags.COMM.data.language, text: parseInt(tags.COMM.data.text) }, nodeTagsMissingValues.comment) assert.strictEqual(tags.COMM.data.short_description, '') expect(tags.TXXX.map((t) => { return { value: t.data.data } })).to.have.deep.members(nodeTagsMissingValues.userDefinedText) tags.TXXX.forEach((t) => { assert.strictEqual(t.data.user_description, '') }) assert.deepStrictEqual({ mime: tags.APIC.data.format, imageBuffer: Buffer.from(tags.APIC.data.data) }, nodeTagsMissingValues.image) assert.strictEqual(tags.APIC.data.description, '') /* POPM seems broken in jsmediatags, data is null but tag looks correct */ /* PRIV seems broken in jsmediatags, data is null but tag looks correct */ assert.deepStrictEqual({ elementID: nodeTagsMissingValues.chapter[0].elementID, startTimeMs: tags.CHAP.data.startTime, endTimeMs: tags.CHAP.data.endTime }, nodeTagsMissingValues.chapter[0]) assert.deepStrictEqual(tags.CHAP.data.subFrames, {}) assert.strictEqual(tags.CHAP.data.startOffset, 0xFFFFFFFF) assert.strictEqual(tags.CHAP.data.endOffset, 0xFFFFFFFF) assert.deepStrictEqual({ elementID: nodeTagsMissingValues.tableOfContents[0].elementID, elements: tags.CTOC.data.childElementIds, }, nodeTagsMissingValues.tableOfContents[0]) assert.deepStrictEqual(tags.CTOC.data.subFrames, {}) assert.strictEqual(tags.CTOC.data.ordered, false) }, onError: function(error) { throw error } }) }) it('read from full self-created tags', function() { const tagsBuffer = NodeID3.create(nodeTagsFull) const read = NodeID3.read(tagsBuffer) delete read.raw delete read.chapter[0].tags.raw delete read.tableOfContents[0].tags.raw read.comment.text = parseInt(read.comment.text) delete read.image.type read.private[0].data = read.private[0].data.toString() if(!read.unsynchronisedLyrics.shortText) delete read.unsynchronisedLyrics.shortText assert.deepStrictEqual(nodeTagsFull, read) }) it('read from missing values self-created tags', function() { const tagsBuffer = NodeID3.create(nodeTagsMissingValues) const read = NodeID3.read(tagsBuffer) delete read.raw assert.deepStrictEqual(read.chapter[0].tags.raw, {}) delete read.chapter[0].tags read.comment.text = parseInt(read.comment.text)