Zazama /
node-id3
| 1 | const fs = require('fs')
|
||
| 2 | const ID3Definitions = require("./src/ID3Definitions")
|
||
| 3 | const ID3Frames = require('./src/ID3Frames')
|
||
| 4 | const ID3Util = require('./src/ID3Util')
|
||
| 5 | const zlib = require('zlib')
|
||
| 6 | const { isFunction, isString } = require('./src/util')
|
||
| 7 | |||
| 8 | /* |
||
| 9 | ** Used specification: http://id3.org/id3v2.3.0 |
||
| 10 | */ |
||
| 11 | |||
| 12 | function writeInBuffer(tags, buffer, fn) {
|
||
| 13 | buffer = removeTagsFromBuffer(buffer) || buffer |
||
| 14 | const completeBuffer = Buffer.concat([tags, buffer]) |
||
| 15 | if(isFunction(fn)) {
|
||
| 16 | fn(null, completeBuffer) |
||
| 17 | return undefined |
||
| 18 | } |
||
| 19 | return completeBuffer |
||
| 20 | } |
||
| 21 | |||
| 22 | function writeAsync(tags, filename, fn) {
|
||
| 23 | try {
|
||
| 24 | fs.readFile(filename, (error, data) => {
|
||
| 25 | if(error) {
|
||
| 26 | fn(error) |
||
| 27 | return |
||
| 28 | } |
||
| 29 | data = removeTagsFromBuffer(data) || data |
||
| 30 | const newData = Buffer.concat([tags, data]) |
||
| 31 | fs.writeFile(filename, newData, 'binary', (error) => {
|
||
| 32 | fn(error) |
||
| 33 | }) |
||
| 34 | }) |
||
| 35 | } catch(error) {
|
||
| 36 | fn(error) |
||
| 37 | } |
||
| 38 | } |
||
| 39 | |||
| 40 | function writeSync(tags, filename) {
|
||
| 41 | try {
|
||
| 42 | let data = fs.readFileSync(filename) |
||
| 43 | data = removeTagsFromBuffer(data) || data |
||
| 44 | const newData = Buffer.concat([tags, data]) |
||
| 45 | fs.writeFileSync(filename, newData, 'binary') |
||
| 46 | } catch(error) {
|
||
| 47 | return error |
||
| 48 | } |
||
| 49 | return true |
||
| 50 | } |
||
| 51 | |||
| 52 | /** |
||
| 53 | * Write passed tags to a file/buffer |
||
| 54 | * @param tags - Object containing tags to be written |
||
| 55 | * @param filebuffer - Can contain a filepath string or buffer |
||
| 56 | * @param fn - (optional) Function for async version |
||
| 57 | * @returns {boolean|Buffer|Error}
|
||
| 58 | */ |
||
| 59 | module.exports.write = function(tags, filebuffer, fn) {
|
||
| 60 | const completeTags = this.create(tags) |
||
| 61 | |||
| 62 | if(filebuffer instanceof Buffer) {
|
||
| 63 | return writeInBuffer(completeTags, filebuffer, fn) |
||
| 64 | } |
||
| 65 | if(isFunction(fn)) {
|
||
| 66 | return writeAsync(completeTags, filebuffer, fn) |
||
| 67 | } |
||
| 68 | return writeSync(completeTags, filebuffer) |
||
| 69 | } |
||
| 70 | |||
| 71 | /** |
||
| 72 | * Creates a buffer containing the ID3 Tag |
||
| 73 | * @param tags - Object containing tags to be written |
||
| 74 | * @param fn fn - (optional) Function for async version |
||
| 75 | * @returns {Buffer}
|
||
| 76 | */ |
||
| 77 | module.exports.create = function(tags, fn) {
|
||
| 78 | const frames = this.createBuffersFromTags(tags) |
||
| 79 | |||
| 80 | // Calculate ID3 body frames size for the header |
||
| 81 | const framesSize = frames.reduce((size, {length}) => size + length, 0)
|
||
| 82 | |||
| 83 | // Create ID3 header |
||
| 84 | const header = Buffer.alloc(10) |
||
| 85 | header.fill(0) |
||
| 86 | header.write("ID3", 0) //File identifier
|
||
| 87 | header.writeUInt16BE(0x0300, 3) //Version 2.3.0 -- 03 00 |
||
| 88 | header.writeUInt16BE(0x0000, 5) //Flags 00 |
||
| 89 | ID3Util.encodeSize(framesSize).copy(header, 6) |
||
| 90 | |||
| 91 | const id3Data = [header].concat(frames) |
||
| 92 | |||
| 93 | if(isFunction(fn)) {
|
||
| 94 | fn(Buffer.concat(id3Data)) |
||
| 95 | return undefined |
||
| 96 | } |
||
| 97 | return Buffer.concat(id3Data) |
||
| 98 | } |
||
| 99 | |||
| 100 | /** |
||
| 101 | * Returns array of buffers created by tags specified in the tags argument |
||
| 102 | * @param tags - Object containing tags to be written |
||
| 103 | * @returns {Array}
|
||
| 104 | */ |
||
| 105 | module.exports.createBuffersFromTags = function(tags) {
|
||
| 106 | let frames = [] |
||
| 107 | if(!tags) {
|
||
| 108 | return frames |
||
| 109 | } |
||
| 110 | const rawObject = Object.keys(tags).reduce((acc, val) => {
|
||
| 111 | if(ID3Definitions.FRAME_IDENTIFIERS.v3[val] !== undefined) {
|
||
| 112 | acc[ID3Definitions.FRAME_IDENTIFIERS.v3[val]] = tags[val] |
||
| 113 | } else if(ID3Definitions.FRAME_IDENTIFIERS.v4[val] !== undefined) {
|
||
| 114 | /** |
||
| 115 | * Currently, node-id3 always writes ID3 version 3. |
||
| 116 | * However, version 3 and 4 are very similar, and node-id3 can also read version 4 frames. |
||
| 117 | * Until version 4 is fully supported, as a workaround, allow writing version 4 frames into a version 3 tag. |
||
| 118 | * If a reader does not support a v4 frame, it's (per spec) supposed to skip it, so it should not be a problem. |
||
| 119 | */ |
||
| 120 | acc[ID3Definitions.FRAME_IDENTIFIERS.v4[val]] = tags[val] |
||
| 121 | } else {
|
||
| 122 | acc[val] = tags[val] |
||
| 123 | } |
||
| 124 | return acc |
||
| 125 | }, {})
|
||
| 126 | |||
| 127 | Object.keys(rawObject).forEach((specName) => {
|
||
| 128 | let frame |
||
| 129 | // Check if invalid specName |
||
| 130 | if(specName.length !== 4) {
|
||
| 131 | return |
||
| 132 | } |
||
| 133 | if(ID3Frames[specName] !== undefined) {
|
||
| 134 | frame = ID3Frames[specName].create(rawObject[specName], 3, this) |
||
| 135 | } else if(specName.startsWith('T')) {
|
||
| 136 | frame = ID3Frames.GENERIC_TEXT.create(specName, rawObject[specName], 3) |
||
| 137 | } else if(specName.startsWith('W')) {
|
||
| 138 | if(ID3Util.getSpecOptions(specName, 3).multiple && rawObject[specName] instanceof Array && rawObject[specName].length > 0) {
|
||
| 139 | frame = Buffer.alloc(0) |
||
| 140 | // deduplicate array |
||
| 141 | for(let url of [...new Set(rawObject[specName])]) {
|
||
| 142 | frame = Buffer.concat([frame, ID3Frames.GENERIC_URL.create(specName, url, 3)]) |
||
| 143 | } |
||
| 144 | } else {
|
||
| 145 | frame = ID3Frames.GENERIC_URL.create(specName, rawObject[specName], 3) |
||
| 146 | } |
||
| 147 | } |
||
| 148 | |||
| 149 | if (frame && frame instanceof Buffer) {
|
||
| 150 | frames.push(frame) |
||
| 151 | } |
||
| 152 | }) |
||
| 153 | |||
| 154 | return frames |
||
| 155 | } |
||
| 156 | |||
| 157 | function readSync(filebuffer, options) {
|
||
| 158 | if(isString(filebuffer)) {
|
||
| 159 | filebuffer = fs.readFileSync(filebuffer) |
||
| 160 | } |
||
| 161 | return this.getTagsFromBuffer(filebuffer, options) |
||
| 162 | } |
||
| 163 | |||
| 164 | function readAsync(filebuffer, options, fn) {
|
||
| 165 | if(isString(filebuffer)) {
|
||
| 166 | fs.readFile(filebuffer, (error, data) => {
|
||
| 167 | if(error) {
|
||
| 168 | fn(error, null) |
||
| 169 | } else {
|
||
| 170 | fn(null, this.getTagsFromBuffer(data, options)) |
||
| 171 | } |
||
| 172 | }) |
||
| 173 | } else {
|
||
| 174 | fn(null, this.getTagsFromBuffer(filebuffer, options)) |
||
| 175 | } |
||
| 176 | } |
||
| 177 | |||
| 178 | /** |
||
| 179 | * Read ID3-Tags from passed buffer/filepath |
||
| 180 | * @param filebuffer - Can contain a filepath string or buffer |
||
| 181 | * @param options - (optional) Object containing options |
||
| 182 | * @param fn - (optional) Function for async version |
||
| 183 | * @returns {boolean}
|
||
| 184 | */ |
||
| 185 | module.exports.read = function(filebuffer, options, fn) {
|
||
| 186 | if(!options || typeof options === 'function') {
|
||
| 187 | fn = fn || options |
||
| 188 | options = {}
|
||
| 189 | } |
||
| 190 | if(isFunction(fn)) {
|
||
| 191 | return readAsync.bind(this)(filebuffer, options, fn) |
||
| 192 | } |
||
| 193 | return readSync.bind(this)(filebuffer, options) |
||
| 194 | } |
||
| 195 | |||
| 196 | /** |
||
| 197 | * Update ID3-Tags from passed buffer/filepath |
||
| 198 | * @param tags - Object containing tags to be written |
||
| 199 | * @param filebuffer - Can contain a filepath string or buffer |
||
| 200 | * @param options - (optional) Object containing options |
||
| 201 | * @param fn - (optional) Function for async version |
||
| 202 | * @returns {boolean|Buffer|Error}
|
||
| 203 | */ |
||
| 204 | module.exports.update = function(tags, filebuffer, options, fn) {
|
||
| 205 | if(!options || typeof options === 'function') {
|
||
| 206 | fn = fn || options |
||
| 207 | options = {}
|
||
| 208 | } |
||
| 209 | |||
| 210 | const rawTags = Object.keys(tags).reduce((acc, val) => {
|
||
| 211 | if(ID3Definitions.FRAME_IDENTIFIERS.v3[val] !== undefined) {
|
||
| 212 | acc[ID3Definitions.FRAME_IDENTIFIERS.v3[val]] = tags[val] |
||
| 213 | } else {
|
||
| 214 | acc[val] = tags[val] |
||
| 215 | } |
||
| 216 | return acc |
||
| 217 | }, {})
|
||
| 218 | |||
| 219 | const updateFn = (currentTags) => {
|
||
| 220 | currentTags = currentTags.raw || {}
|
||
| 221 | Object.keys(rawTags).map((specName) => {
|
||
| 222 | const options = ID3Util.getSpecOptions(specName, 3) |
||
| 223 | const cCompare = {}
|
||
| 224 | if(options.multiple && currentTags[specName] && rawTags[specName]) {
|
||
| 225 | if(options.updateCompareKey) {
|
||
| 226 | currentTags[specName].forEach((cTag, index) => {
|
||
| 227 | cCompare[cTag[options.updateCompareKey]] = index |
||
| 228 | }) |
||
| 229 | |||
| 230 | } |
||
| 231 | if (!(rawTags[specName] instanceof Array)) {
|
||
| 232 | rawTags[specName] = [rawTags[specName]] |
||
| 233 | } |
||
| 234 | rawTags[specName].forEach((rTag) => {
|
||
| 235 | const comparison = cCompare[rTag[options.updateCompareKey]] |
||
| 236 | if (comparison !== undefined) {
|
||
| 237 | currentTags[specName][comparison] = rTag |
||
| 238 | } else {
|
||
| 239 | currentTags[specName].push(rTag) |
||
| 240 | } |
||
| 241 | }) |
||
| 242 | } else {
|
||
| 243 | currentTags[specName] = rawTags[specName] |
||
| 244 | } |
||
| 245 | }) |
||
| 246 | |||
| 247 | return currentTags |
||
| 248 | } |
||
| 249 | |||
| 250 | if(!isFunction(fn)) {
|
||
| 251 | return this.write(updateFn(this.read(filebuffer, options)), filebuffer) |
||
| 252 | } |
||
| 253 | |||
| 254 | this.write(updateFn(this.read(filebuffer, options)), filebuffer, fn) |
||
| 255 | } |
||
|
0 ignored issues
–
show
Best Practice
introduced
by
Loading history...
|
|||
| 256 | |||
| 257 | module.exports.getTagsFromBuffer = function(filebuffer, options) {
|
||
| 258 | let framePosition = ID3Util.getFramePosition(filebuffer) |
||
| 259 | if(framePosition === -1) {
|
||
| 260 | return this.getTagsFromFrames([], 3, options) |
||
| 261 | } |
||
| 262 | const frameSize = ID3Util.decodeSize(filebuffer.slice(framePosition + 6, framePosition + 10)) + 10 |
||
| 263 | let ID3Frame = Buffer.alloc(frameSize + 1) |
||
| 264 | filebuffer.copy(ID3Frame, 0, framePosition) |
||
| 265 | //ID3 version e.g. 3 if ID3v2.3.0 |
||
| 266 | let ID3Version = ID3Frame[3] |
||
| 267 | const tagFlags = ID3Util.parseTagHeaderFlags(ID3Frame) |
||
| 268 | let extendedHeaderOffset = 0 |
||
| 269 | if(tagFlags.extendedHeader) {
|
||
| 270 | if(ID3Version === 3) {
|
||
| 271 | extendedHeaderOffset = 4 + filebuffer.readUInt32BE(10) |
||
| 272 | } else if(ID3Version === 4) {
|
||
| 273 | extendedHeaderOffset = ID3Util.decodeSize(filebuffer.slice(10, 14)) |
||
| 274 | } |
||
| 275 | } |
||
| 276 | let ID3FrameBody = Buffer.alloc(frameSize - 10 - extendedHeaderOffset) |
||
| 277 | filebuffer.copy(ID3FrameBody, 0, framePosition + 10 + extendedHeaderOffset) |
||
| 278 | |||
| 279 | let frames = this.getFramesFromID3Body(ID3FrameBody, ID3Version, options) |
||
| 280 | |||
| 281 | return this.getTagsFromFrames(frames, ID3Version, options) |
||
| 282 | } |
||
| 283 | |||
| 284 | module.exports.getFramesFromID3Body = function(ID3FrameBody, ID3Version, options = {}) {
|
||
| 285 | let currentPosition = 0 |
||
| 286 | let frames = [] |
||
| 287 | if(!ID3FrameBody || !(ID3FrameBody instanceof Buffer)) {
|
||
| 288 | return frames |
||
| 289 | } |
||
| 290 | |||
| 291 | let identifierSize = 4 |
||
| 292 | let textframeHeaderSize = 10 |
||
| 293 | if(ID3Version === 2) {
|
||
| 294 | identifierSize = 3 |
||
| 295 | textframeHeaderSize = 6 |
||
| 296 | } |
||
| 297 | |||
| 298 | while(currentPosition < ID3FrameBody.length && ID3FrameBody[currentPosition] !== 0x00) {
|
||
| 299 | let bodyFrameHeader = Buffer.alloc(textframeHeaderSize) |
||
| 300 | ID3FrameBody.copy(bodyFrameHeader, 0, currentPosition) |
||
| 301 | |||
| 302 | let decodeSize = false |
||
| 303 | if(ID3Version === 4) {
|
||
| 304 | decodeSize = true |
||
| 305 | } |
||
| 306 | let bodyFrameSize = ID3Util.getFrameSize(bodyFrameHeader, decodeSize, ID3Version) |
||
| 307 | if(bodyFrameSize + 10 > (ID3FrameBody.length - currentPosition)) {
|
||
| 308 | break |
||
| 309 | } |
||
| 310 | const specName = bodyFrameHeader.toString('utf8', 0, identifierSize)
|
||
| 311 | if(options.exclude instanceof Array && options.exclude.includes(specName) || options.include instanceof Array && !options.include.includes(specName)) {
|
||
| 312 | currentPosition += bodyFrameSize + textframeHeaderSize |
||
| 313 | continue |
||
| 314 | } |
||
| 315 | const frameHeaderFlags = ID3Util.parseFrameHeaderFlags(bodyFrameHeader, ID3Version) |
||
| 316 | let bodyFrameBuffer = Buffer.alloc(bodyFrameSize) |
||
| 317 | ID3FrameBody.copy(bodyFrameBuffer, 0, currentPosition + textframeHeaderSize + (frameHeaderFlags.dataLengthIndicator ? 4 : 0)) |
||
| 318 | // Size of sub frame + its header |
||
| 319 | currentPosition += bodyFrameSize + textframeHeaderSize |
||
| 320 | frames.push({
|
||
| 321 | name: specName, |
||
| 322 | flags: frameHeaderFlags, |
||
| 323 | body: frameHeaderFlags.unsynchronisation ? ID3Util.processUnsynchronisedBuffer(bodyFrameBuffer) : bodyFrameBuffer |
||
| 324 | }) |
||
| 325 | } |
||
| 326 | |||
| 327 | return frames |
||
| 328 | } |
||
| 329 | |||
| 330 | module.exports.getTagsFromFrames = function(frames, ID3Version, options = {}) {
|
||
| 331 | let tags = { }
|
||
| 332 | let raw = { }
|
||
| 333 | |||
| 334 | frames.forEach((frame) => {
|
||
| 335 | let specName |
||
| 336 | let identifier |
||
| 337 | if(ID3Version === 2) {
|
||
| 338 | specName = ID3Definitions.FRAME_IDENTIFIERS.v3[ID3Definitions.FRAME_INTERNAL_IDENTIFIERS.v2[frame.name]] |
||
| 339 | identifier = ID3Definitions.FRAME_INTERNAL_IDENTIFIERS.v2[frame.name] |
||
| 340 | } else if(ID3Version === 3 || ID3Version === 4) {
|
||
| 341 | /** |
||
| 342 | * Due to their similarity, it's possible to mix v3 and v4 frames even if they don't exist in their corrosponding spec. |
||
| 343 | * Programs like Mp3tag allow you to do so, so we should allow reading e.g. v4 frames from a v3 ID3 Tag |
||
| 344 | */ |
||
| 345 | specName = frame.name |
||
| 346 | identifier = ID3Definitions.FRAME_INTERNAL_IDENTIFIERS.v3[frame.name] || ID3Definitions.FRAME_INTERNAL_IDENTIFIERS.v4[frame.name] |
||
| 347 | } |
||
| 348 | |||
| 349 | if(!specName || !identifier || frame.flags.encryption) {
|
||
| 350 | return |
||
| 351 | } |
||
| 352 | |||
| 353 | if(frame.flags.compression) {
|
||
| 354 | if(frame.body.length < 5) {
|
||
| 355 | return |
||
| 356 | } |
||
| 357 | const inflatedSize = frame.body.readInt32BE() |
||
| 358 | /* |
||
| 359 | * ID3 spec defines that compression is stored in ZLIB format, but doesn't specify if header is present or not. |
||
| 360 | * ZLIB has a 2-byte header. |
||
| 361 | * 1. try if header + body decompression |
||
| 362 | * 2. else try if header is not stored (assume that all content is deflated "body") |
||
| 363 | * 3. else try if inflation works if the header is omitted (implementation dependent) |
||
| 364 | * */ |
||
| 365 | try {
|
||
| 366 | frame.body = zlib.inflateSync(frame.body.slice(4)) |
||
| 367 | } catch (e) {
|
||
| 368 | try {
|
||
| 369 | frame.body = zlib.inflateRawSync(frame.body.slice(4)) |
||
| 370 | } catch (e) {
|
||
| 371 | try {
|
||
| 372 | frame.body = zlib.inflateRawSync(frame.body.slice(6)) |
||
| 373 | } catch (e) {
|
||
| 374 | return |
||
| 375 | } |
||
| 376 | } |
||
| 377 | } |
||
| 378 | if(frame.body.length !== inflatedSize) {
|
||
| 379 | return |
||
| 380 | } |
||
| 381 | } |
||
| 382 | |||
| 383 | let decoded |
||
| 384 | if(ID3Frames[specName]) {
|
||
| 385 | decoded = ID3Frames[specName].read(frame.body, ID3Version, this) |
||
| 386 | } else if(specName.startsWith('T')) {
|
||
| 387 | decoded = ID3Frames.GENERIC_TEXT.read(frame.body, ID3Version) |
||
| 388 | } else if(specName.startsWith('W')) {
|
||
| 389 | decoded = ID3Frames.GENERIC_URL.read(frame.body, ID3Version) |
||
| 390 | } |
||
| 391 | |||
| 392 | if(decoded) {
|
||
| 393 | if(ID3Util.getSpecOptions(specName, ID3Version).multiple) {
|
||
| 394 | if(!options.onlyRaw) {
|
||
| 395 | if(!tags[identifier]) {
|
||
| 396 | tags[identifier] = [] |
||
| 397 | } |
||
| 398 | tags[identifier].push(decoded) |
||
| 399 | } |
||
| 400 | if(!options.noRaw) {
|
||
| 401 | if(!raw[specName]) {
|
||
| 402 | raw[specName] = [] |
||
| 403 | } |
||
| 404 | raw[specName].push(decoded) |
||
| 405 | } |
||
| 406 | } else {
|
||
| 407 | if(!options.onlyRaw) {
|
||
| 408 | tags[identifier] = decoded |
||
| 409 | } |
||
| 410 | if(!options.noRaw) {
|
||
| 411 | raw[specName] = decoded |
||
| 412 | } |
||
| 413 | } |
||
| 414 | } |
||
| 415 | }) |
||
| 416 | |||
| 417 | if(options.onlyRaw) {
|
||
| 418 | return raw |
||
| 419 | } |
||
| 420 | if(options.noRaw) {
|
||
| 421 | return tags |
||
| 422 | } |
||
| 423 | |||
| 424 | tags.raw = raw |
||
| 425 | return tags |
||
| 426 | } |
||
| 427 | |||
| 428 | module.exports.removeTagsFromBuffer = removeTagsFromBuffer |
||
| 429 | |||
| 430 | /** |
||
| 431 | * Checks and removes already written ID3-Frames from a buffer |
||
| 432 | * @param {Buffer} data
|
||
| 433 | * @returns {boolean|Buffer}
|
||
| 434 | */ |
||
| 435 | function removeTagsFromBuffer(data) {
|
||
| 436 | const framePosition = ID3Util.getFramePosition(data) |
||
| 437 | |||
| 438 | if (framePosition === -1) {
|
||
| 439 | return data |
||
| 440 | } |
||
| 441 | |||
| 442 | const encodedSize = data.slice(framePosition + 6, framePosition + 10) |
||
| 443 | if (!ID3Util.isValidEncodedSize(encodedSize)) {
|
||
| 444 | return false |
||
| 445 | } |
||
| 446 | |||
| 447 | if (data.length >= framePosition + 10) {
|
||
| 448 | const size = ID3Util.decodeSize(encodedSize) |
||
| 449 | return Buffer.concat([ |
||
| 450 | data.slice(0, framePosition), |
||
| 451 | data.slice(framePosition + size + 10) |
||
| 452 | ]) |
||
| 453 | } |
||
| 454 | |||
| 455 | return data |
||
| 456 | } |
||
| 457 | |||
| 458 | /** |
||
| 459 | * @param {string} filepath - Filepath to file
|
||
| 460 | * @returns {boolean|Error}
|
||
| 461 | */ |
||
| 462 | function removeTagsSync(filepath) {
|
||
| 463 | let data |
||
| 464 | try {
|
||
| 465 | data = fs.readFileSync(filepath) |
||
| 466 | } catch(error) {
|
||
| 467 | return error |
||
| 468 | } |
||
| 469 | |||
| 470 | const newData = removeTagsFromBuffer(data) |
||
| 471 | if(!newData) {
|
||
| 472 | return false |
||
| 473 | } |
||
| 474 | |||
| 475 | try {
|
||
| 476 | fs.writeFileSync(filepath, newData, 'binary') |
||
| 477 | } catch(error) {
|
||
| 478 | return error |
||
| 479 | } |
||
| 480 | |||
| 481 | return true |
||
| 482 | } |
||
| 483 | |||
| 484 | /** |
||
| 485 | * @param {string} filepath - Filepath to file
|
||
| 486 | * @param {(error: Error) => void} fn - Function for async usage
|
||
| 487 | * @returns {void}
|
||
| 488 | */ |
||
| 489 | function removeTagsAsync(filepath, fn) {
|
||
| 490 | fs.readFile(filepath, (error, data) => {
|
||
| 491 | if(error) {
|
||
| 492 | fn(error) |
||
| 493 | return |
||
| 494 | } |
||
| 495 | |||
| 496 | const newData = removeTagsFromBuffer(data) |
||
| 497 | if(!newData) {
|
||
| 498 | fn(error) |
||
| 499 | return |
||
| 500 | } |
||
| 501 | |||
| 502 | fs.writeFile(filepath, newData, 'binary', (error) => {
|
||
| 503 | if(error) {
|
||
| 504 | fn(error) |
||
| 505 | } else {
|
||
| 506 | fn(null) |
||
| 507 | } |
||
| 508 | }) |
||
| 509 | }) |
||
| 510 | } |
||
| 511 | |||
| 512 | /** |
||
| 513 | * Checks and removes already written ID3-Frames from a file |
||
| 514 | * @param {string} filepath - Filepath to file
|
||
| 515 | * @param fn - (optional) Function for async usage |
||
| 516 | * @returns {boolean|Error}
|
||
| 517 | */ |
||
| 518 | module.exports.removeTags = function(filepath, fn) {
|
||
| 519 | if(isFunction(fn)) {
|
||
| 520 | return removeTagsAsync(filepath, fn) |
||
| 521 | } |
||
| 522 | return removeTagsSync(filepath) |
||
| 523 | } |
||
| 524 | |||
| 525 | function makeSwapParameters(fn) {
|
||
| 526 | return (a, b) => fn(b, a) |
||
| 527 | } |
||
| 528 | |||
| 529 | // The reorderParameter is a workaround because the callback function |
||
| 530 | // does not have a consistent interface between all the API functions. |
||
| 531 | // Ideally, all the functions should align with the promise style and |
||
| 532 | // always have the result first and the error second. |
||
| 533 | // Changing this would break the current public API. |
||
| 534 | // This could be changed internally and swap the parameter in a light |
||
| 535 | // wrapper when creating the public interface and then remove in a |
||
| 536 | // version 1.0 later with an API breaking change. |
||
| 537 | function makePromise( |
||
| 538 | fn, |
||
| 539 | reorderParameters = fn => (a, b) => fn(a, b) |
||
| 540 | ) {
|
||
| 541 | return new Promise((resolve, reject) => {
|
||
| 542 | fn(reorderParameters((error, result) => {
|
||
| 543 | if(error) {
|
||
| 544 | reject(error) |
||
| 545 | } else {
|
||
| 546 | resolve(result) |
||
| 547 | } |
||
| 548 | })) |
||
| 549 | }) |
||
| 550 | } |
||
| 551 | |||
| 552 | module.exports.Promise = {
|
||
| 553 | write: (tags, file) => makePromise(this.write.bind(this, tags, file)), |
||
| 554 | update: (tags, file) => makePromise(this.update.bind(this, tags, file)), |
||
| 555 | create: (tags) => makePromise(this.create.bind(this, tags), makeSwapParameters), |
||
| 556 | read: (file, options) => makePromise(this.read.bind(this, file, options)), |
||
| 557 | removeTags: (filepath) => makePromise(this.removeTags.bind(this, filepath)) |
||
| 558 | } |
||
| 559 | |||
| 560 | module.exports.Constants = ID3Definitions.Constants |
||
| 561 |