node-id3
Version:
Pure JavaScript ID3v2 Tag writer and reader
1,221 lines (1,098 loc) • 51.3 kB
JavaScript
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)