Comparing version 0.2.3 to 0.2.4
# Changelog | ||
## [0.2.3] - 2020-04-30 | ||
## [0.2.4] - 2022-11-09 | ||
- Add synchronised lyrics (SYLT frame) (by @pbricout) | ||
## [0.2.3] - 2021-04-30 | ||
- Don't change APIC mime type on read | ||
- Fix unsynchronisation implementation | ||
## [0.2.2] - 2020-01-01 | ||
## [0.2.2] - 2021-01-01 | ||
@@ -205,3 +208,4 @@ ### Fixed | ||
[unreleased]: https://github.com/Zazama/node-id3/compare/0.2.3...HEAD | ||
[unreleased]: https://github.com/Zazama/node-id3/compare/0.2.4...HEAD | ||
[0.2.4]: https://github.com/Zazama/node-id3/compare/0.2.3...0.2.4 | ||
[0.2.3]: https://github.com/Zazama/node-id3/compare/0.2.2...0.2.3 | ||
@@ -208,0 +212,0 @@ [0.2.2]: https://github.com/Zazama/node-id3/compare/0.2.1...0.2.2 |
@@ -258,3 +258,42 @@ declare module "node-id3" { | ||
text: string | ||
} | ||
}, | ||
/** | ||
* SYLT frames | ||
* | ||
* @see {@link https://id3.org/d3v2.3.0 4.10. Synchronised lyrics/text} | ||
*/ | ||
synchronisedLyrics?: Array<{ | ||
/** | ||
* 3 letter ISO 639-2 language code, for example: eng | ||
* @see {@link https://id3.org/ISO%20639-2 ISO 639-2} | ||
*/ | ||
language: string, | ||
/** | ||
* Absolute time: | ||
* - 1: MPEG frames unit | ||
* - 2: milliseconds unit | ||
*/ | ||
timeStampFormat: number, | ||
/** | ||
* - 0: other | ||
* - 1: lyrics | ||
* - 2: text transcription | ||
* - 3: movement/part name (e.g. "Adagio") | ||
* - 4: events (e.g. "Don Quijote enters the stage") | ||
* - 5: chord (e.g. "Bb F Fsus") | ||
* - 6: is trivia/'pop up' information | ||
*/ | ||
contentType: number, | ||
/** | ||
* Content descriptor | ||
*/ | ||
shortText?: string, | ||
synchronisedText: Array<{ | ||
text: string, | ||
/** | ||
* Absolute time in unit according to `timeStampFormat`. | ||
*/ | ||
timeStamp: number | ||
}> | ||
}>, | ||
userDefinedText?: [{ | ||
@@ -261,0 +300,0 @@ description: string, |
145
index.js
@@ -5,2 +5,3 @@ const fs = require('fs') | ||
const ID3Util = require('./src/ID3Util') | ||
const zlib = require('zlib') | ||
@@ -25,3 +26,3 @@ /* | ||
fn(null, completeBuffer) | ||
return | ||
return undefined | ||
} else { | ||
@@ -114,3 +115,5 @@ return completeBuffer | ||
let frames = [] | ||
if(!tags) return frames | ||
if(!tags) { | ||
return frames | ||
} | ||
const rawObject = Object.keys(tags).reduce((acc, val) => { | ||
@@ -125,3 +128,3 @@ if(ID3Definitions.FRAME_IDENTIFIERS.v3[val] !== undefined) { | ||
Object.keys(rawObject).forEach((specName, index) => { | ||
Object.keys(rawObject).forEach((specName) => { | ||
let frame | ||
@@ -148,3 +151,3 @@ // Check if invalid specName | ||
if (frame instanceof Buffer) { | ||
if (frame && frame instanceof Buffer) { | ||
frames.push(frame) | ||
@@ -224,4 +227,6 @@ } | ||
} | ||
if (!(rawTags[specName] instanceof Array)) rawTags[specName] = [rawTags[specName]] | ||
rawTags[specName].forEach((rTag, index) => { | ||
if (!(rawTags[specName] instanceof Array)) { | ||
rawTags[specName] = [rawTags[specName]] | ||
} | ||
rawTags[specName].forEach((rTag) => { | ||
const comparison = cCompare[rTag[options.updateCompareKey]] | ||
@@ -244,7 +249,5 @@ if (comparison !== undefined) { | ||
return this.write(updateFn(this.read(filebuffer, options)), filebuffer) | ||
} else { | ||
this.read(filebuffer, (err, currentTags) => { | ||
this.write(updateFn(this.read(filebuffer, options)), filebuffer, fn) | ||
}) | ||
} | ||
this.write(updateFn(this.read(filebuffer, options)), filebuffer, fn) | ||
} | ||
@@ -329,10 +332,40 @@ | ||
frames.forEach((frame, index) => { | ||
frames.forEach((frame) => { | ||
const specName = ID3Version === 2 ? ID3Definitions.FRAME_IDENTIFIERS.v3[ID3Definitions.FRAME_INTERNAL_IDENTIFIERS.v2[frame.name]] : frame.name | ||
const identifier = ID3Version === 2 ? ID3Definitions.FRAME_INTERNAL_IDENTIFIERS.v2[frame.name] : ID3Definitions.FRAME_INTERNAL_IDENTIFIERS.v3[frame.name] | ||
if(!specName || !identifier) { | ||
if(!specName || !identifier || frame.flags.encryption) { | ||
return | ||
} | ||
if(frame.flags.compression) { | ||
if(frame.body.length < 5) { | ||
return | ||
} | ||
const inflatedSize = frame.body.readInt32BE() | ||
/* | ||
* ID3 spec defines that compression is stored in ZLIB format, but doesn't specify if header is present or not. | ||
* ZLIB has a 2-byte header. | ||
* 1. try if header + body decompression | ||
* 2. else try if header is not stored (assume that all content is deflated "body") | ||
* 3. else try if inflation works if the header is omitted (implementation dependent) | ||
* */ | ||
try { | ||
frame.body = zlib.inflateSync(frame.body.slice(4)) | ||
} catch (e) { | ||
try { | ||
frame.body = zlib.inflateRawSync(frame.body.slice(4)) | ||
} catch (e) { | ||
try { | ||
frame.body = zlib.inflateRawSync(frame.body.slice(6)) | ||
} catch (e) { | ||
return | ||
} | ||
} | ||
} | ||
if(frame.body.length !== inflatedSize) { | ||
return | ||
} | ||
} | ||
let decoded | ||
@@ -350,7 +383,11 @@ if(ID3Frames[specName]) { | ||
if(!options.onlyRaw) { | ||
if(!tags[identifier]) tags[identifier] = [] | ||
if(!tags[identifier]) { | ||
tags[identifier] = [] | ||
} | ||
tags[identifier].push(decoded) | ||
} | ||
if(!options.noRaw) { | ||
if(!raw[specName]) raw[specName] = [] | ||
if(!raw[specName]) { | ||
raw[specName] = [] | ||
} | ||
raw[specName].push(decoded) | ||
@@ -369,4 +406,8 @@ } | ||
if(options.onlyRaw) return raw | ||
if(options.noRaw) return tags | ||
if(options.onlyRaw) { | ||
return raw | ||
} | ||
if(options.noRaw) { | ||
return tags | ||
} | ||
@@ -385,3 +426,3 @@ tags.raw = raw | ||
if(framePosition === -1) { | ||
if (framePosition === -1) { | ||
return data | ||
@@ -397,8 +438,8 @@ } | ||
if(data.length >= framePosition + 10) { | ||
if (data.length >= framePosition + 10) { | ||
const size = ID3Util.decodeSize(data.slice(framePosition + 6, framePosition + 10)) | ||
return Buffer.concat([data.slice(0, framePosition), data.slice(framePosition + size + 10)]) | ||
} else { | ||
return data | ||
} | ||
return data | ||
} | ||
@@ -433,23 +474,23 @@ | ||
return true | ||
} else { | ||
fs.readFile(filepath, function(err, data) { | ||
} | ||
fs.readFile(filepath, function(err, data) { | ||
if(err) { | ||
fn(err) | ||
} | ||
let newData = this.removeTagsFromBuffer(data) | ||
if(!newData) { | ||
fn(err) | ||
return | ||
} | ||
fs.writeFile(filepath, newData, 'binary', function(err) { | ||
if(err) { | ||
fn(err) | ||
} else { | ||
fn(false) | ||
} | ||
let newData = this.removeTagsFromBuffer(data) | ||
if(!newData) { | ||
fn(err) | ||
return | ||
} | ||
fs.writeFile(filepath, newData, 'binary', function(err) { | ||
if(err) { | ||
fn(err) | ||
} else { | ||
fn(false) | ||
} | ||
}) | ||
}.bind(this)) | ||
} | ||
}) | ||
}.bind(this)) | ||
} | ||
@@ -461,4 +502,7 @@ | ||
this.write(tags, file, (err, ret) => { | ||
if(err) reject(err) | ||
else resolve(ret) | ||
if(err) { | ||
reject(err) | ||
} else { | ||
resolve(ret) | ||
} | ||
}) | ||
@@ -470,4 +514,7 @@ }) | ||
this.update(tags, file, (err, ret) => { | ||
if(err) reject(err) | ||
else resolve(ret) | ||
if(err) { | ||
reject(err) | ||
} else { | ||
resolve(ret) | ||
} | ||
}) | ||
@@ -486,4 +533,7 @@ }) | ||
this.read(file, options, (err, ret) => { | ||
if(err) reject(err) | ||
else resolve(ret) | ||
if(err) { | ||
reject(err) | ||
} else { | ||
resolve(ret) | ||
} | ||
}) | ||
@@ -495,4 +545,7 @@ }) | ||
this.removeTags(filepath, (err) => { | ||
if(err) reject(err) | ||
else resolve() | ||
if(err) { | ||
reject(err) | ||
} else { | ||
resolve() | ||
} | ||
}) | ||
@@ -499,0 +552,0 @@ }) |
{ | ||
"name": "node-id3", | ||
"version": "0.2.3", | ||
"version": "0.2.4", | ||
"description": "Pure JavaScript ID3v2 Tag writer and reader", | ||
@@ -5,0 +5,0 @@ "author": "Jan Metzger <jan.metzger@gmx.net>", |
@@ -36,2 +36,4 @@ # node-id3 | ||
### Write tags to file | ||
If you have an existing file/buffer (e.g. an mp3 file) you can use the write method to write your tags into it. It will remove existing tags and add yours. | ||
```javascript | ||
@@ -51,3 +53,4 @@ const success = NodeID3.write(tags, filepath) // Returns true/Error | ||
### Update existing tags of file or buffer | ||
This will write new/changed values but keep all others | ||
The update method works like the write method but will keep or overwrite existing tags instead of removing them. | ||
```javascript | ||
@@ -72,2 +75,3 @@ const success = NodeID3.update(tags, filepath) // Returns true/Error | ||
### Create tags as buffer | ||
The create method will return a buffer of your ID3-Tag. You can use it to e.g. write it into a file yourself instead of using the write method. | ||
```javascript | ||
@@ -184,2 +188,16 @@ const success = NodeID3.create(tags) // Returns ID3-Tag Buffer | ||
} | ||
// See documentation for more details. | ||
synchronisedLyrics: [{ | ||
language: "eng", | ||
timeStampFormat: 2, // Absolute milliseconds | ||
contentType: 1, // Lyrics | ||
shortText: "Content descriptor", | ||
synchronisedText: [{ | ||
text: "part 1", | ||
timeStamp: 0 | ||
}, { | ||
text: "part 2", | ||
timeStamp: 1000 | ||
}] | ||
}] | ||
userDefinedText: [{ | ||
@@ -290,2 +308,3 @@ description: "txxx name", | ||
unsynchronisedLyrics "USLT" | ||
synchronisedLyrics "SYLT" | ||
userDefinedText "TXXX" | ||
@@ -292,0 +311,0 @@ popularimeter "POPM" |
@@ -89,2 +89,3 @@ const FRAME_IDENTIFIERS = { | ||
unsynchronisedLyrics: "USLT", | ||
synchronisedLyrics: "SYLT", | ||
userDefinedText: "TXXX", | ||
@@ -144,2 +145,5 @@ popularimeter: "POPM", | ||
}, | ||
"SYLT": { | ||
multiple: true | ||
}, | ||
"COMM": { | ||
@@ -146,0 +150,0 @@ multiple: false /* change in 1.0 */ |
@@ -165,2 +165,45 @@ const fs = require('fs') | ||
module.exports.SYLT = { | ||
create: (data) => { | ||
if(!(data instanceof Array)) { | ||
data = [data] | ||
} | ||
const encoding = 1; // 16 bit unicode | ||
return Buffer.concat(data.map(lycics => { | ||
const frameBuilder = new ID3FrameBuilder("SYLT") | ||
.appendStaticNumber(encoding, 1) | ||
.appendStaticValue(lycics.language, 3) | ||
.appendStaticNumber(lycics.timeStampFormat, 1) | ||
.appendStaticNumber(lycics.contentType, 1) | ||
.appendNullTerminatedValue(lycics.shortText, encoding) | ||
lycics.synchronisedText.forEach(part => { | ||
frameBuilder.appendNullTerminatedValue(part.text, encoding) | ||
frameBuilder.appendStaticNumber(part.timeStamp, 4) | ||
}) | ||
return frameBuilder.getBuffer() | ||
})) | ||
}, | ||
read: (buffer) => { | ||
const reader = new ID3FrameReader(buffer, 0) | ||
return { | ||
language: reader.consumeStaticValue('string', 3, 0x00), | ||
timeStampFormat: reader.consumeStaticValue('number', 1), | ||
contentType: reader.consumeStaticValue('number', 1), | ||
shortText: reader.consumeNullTerminatedValue('string'), | ||
synchronisedText: Array.from((function*() { | ||
while(true) { | ||
const text = reader.consumeNullTerminatedValue('string') | ||
const timeStamp = reader.consumeStaticValue('number', 4) | ||
if (text === undefined || timeStamp === undefined) { | ||
break | ||
} | ||
yield {text, timeStamp} | ||
} | ||
})()) | ||
} | ||
} | ||
} | ||
module.exports.TXXX = { | ||
@@ -167,0 +210,0 @@ create: (data) => { |
@@ -186,2 +186,25 @@ const NodeID3 = require('../index.js') | ||
it('create SYLT frame', function() { | ||
let tags = { | ||
synchronisedLyrics: [{ | ||
language: "deu", | ||
timeStampFormat: 2, // Milliseconds | ||
contentType: 1, // 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() { | ||
@@ -474,2 +497,23 @@ const tags = { | ||
it('read SYLT frame', function() { | ||
let frameBuf = Buffer.from('4944330300000000007c53594c54000000720000016465750201fffe48006100690077007300e400e40023000000fffe610073006b00640068002000610073006800640020006f006c00610068007300000000000000fffe65006c006f0077007a00200064006c006f0075006100690073006800200064006b0061006a0068000000000003e8', 'hex') | ||
const synchronisedLyrics = [{ | ||
language: "deu", | ||
timeStampFormat: 2, // Milliseconds | ||
contentType: 1, // 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() { | ||
@@ -476,0 +520,0 @@ let frameBuf = Buffer.from('4944330300000000006E434F4D4D00000064000001646575FFFE48006100690077007300E400E40023000000FFFE610073006B00640068002000610073006800640020006F006C00610068007300200065006C006F0077007A00200064006C006F0075006100690073006800200064006B0061006A006800', 'hex') |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
129604
2755
320