Zazama /
node-id3
| 1 | const fs = require('fs') |
||
| 2 | const ID3FrameBuilder = require("./ID3FrameBuilder") |
||
| 3 | const ID3FrameReader = require("./ID3FrameReader") |
||
| 4 | const ID3Definitions = require("./ID3Definitions") |
||
| 5 | const ID3Util = require("./ID3Util") |
||
| 6 | const ID3Helpers = require('./ID3Helpers') |
||
| 7 | const { isString } = require('./util') |
||
| 8 | |||
| 9 | module.exports.GENERIC_TEXT = { |
||
| 10 | create: (frameIdentifier, data) => { |
||
| 11 | if(!frameIdentifier || !data) { |
||
| 12 | return null |
||
| 13 | } |
||
| 14 | |||
| 15 | return new ID3FrameBuilder(frameIdentifier) |
||
| 16 | .appendStaticNumber(0x01, 0x01) |
||
| 17 | .appendStaticValue(data, null, 0x01) |
||
| 18 | .getBuffer() |
||
| 19 | }, |
||
| 20 | read: (buffer) => { |
||
| 21 | const reader = new ID3FrameReader(buffer, 0) |
||
| 22 | |||
| 23 | return reader.consumeStaticValue('string') |
||
| 24 | } |
||
| 25 | } |
||
| 26 | |||
| 27 | module.exports.GENERIC_URL = { |
||
| 28 | create: (frameIdentifier, data) => { |
||
| 29 | if(!frameIdentifier || !data) { |
||
| 30 | return null |
||
| 31 | } |
||
| 32 | |||
| 33 | return new ID3FrameBuilder(frameIdentifier) |
||
| 34 | .appendStaticValue(data) |
||
| 35 | .getBuffer() |
||
| 36 | }, |
||
| 37 | read: (buffer) => { |
||
| 38 | const reader = new ID3FrameReader(buffer) |
||
| 39 | |||
| 40 | return reader.consumeStaticValue('string') |
||
| 41 | } |
||
| 42 | } |
||
| 43 | |||
| 44 | module.exports.APIC = { |
||
| 45 | create: (data) => { |
||
| 46 | try { |
||
| 47 | if (data instanceof Buffer) { |
||
| 48 | data = { |
||
| 49 | imageBuffer: Buffer.from(data) |
||
| 50 | } |
||
| 51 | } else if (isString(data)) { |
||
| 52 | data = { |
||
| 53 | imageBuffer: fs.readFileSync(data) |
||
| 54 | } |
||
| 55 | } else if (!data.imageBuffer) { |
||
| 56 | return Buffer.alloc(0) |
||
| 57 | } |
||
| 58 | |||
| 59 | let mime_type = data.mime |
||
| 60 | |||
| 61 | if(!mime_type) { |
||
| 62 | mime_type = ID3Util.getPictureMimeTypeFromBuffer(data.imageBuffer) |
||
| 63 | } |
||
| 64 | |||
| 65 | const TagConstants = ID3Definitions.TagConstants.AttachedPicture |
||
| 66 | const pictureType = data.type || {} |
||
| 67 | const pictureTypeId = pictureType.id === undefined |
||
| 68 | ? TagConstants.PictureType.FRONT_COVER : pictureType.id |
||
| 69 | |||
| 70 | /* |
||
| 71 | * Fix a bug in iTunes where the artwork is not recognized when the description is empty using UTF-16. |
||
| 72 | * Instead, if the description is empty, use encoding 0x00 (ISO-8859-1). |
||
| 73 | */ |
||
| 74 | const { description = '' } = data |
||
| 75 | const encoding = description ? 0x01 : 0x00 |
||
| 76 | return new ID3FrameBuilder('APIC') |
||
| 77 | .appendStaticNumber(encoding, 1) |
||
| 78 | .appendNullTerminatedValue(mime_type) |
||
| 79 | .appendStaticNumber(pictureTypeId, 1) |
||
| 80 | .appendNullTerminatedValue(description, encoding) |
||
| 81 | .appendStaticValue(data.imageBuffer) |
||
| 82 | .getBuffer() |
||
| 83 | } catch(error) { |
||
| 84 | return error |
||
| 85 | } |
||
| 86 | }, |
||
| 87 | read: (buffer, version) => { |
||
| 88 | const reader = new ID3FrameReader(buffer, 0) |
||
| 89 | let mime |
||
| 90 | if(version === 2) { |
||
| 91 | mime = reader.consumeStaticValue('string', 3, 0x00) |
||
| 92 | } else { |
||
| 93 | mime = reader.consumeNullTerminatedValue('string', 0x00) |
||
| 94 | } |
||
| 95 | |||
| 96 | const typeId = reader.consumeStaticValue('number', 1) |
||
| 97 | const description = reader.consumeNullTerminatedValue('string') |
||
| 98 | const imageBuffer = reader.consumeStaticValue() |
||
| 99 | |||
| 100 | return { |
||
| 101 | mime: mime, |
||
| 102 | type: { |
||
| 103 | id: typeId, |
||
| 104 | name: ID3Definitions.APIC_TYPES[typeId] |
||
| 105 | }, |
||
| 106 | description: description, |
||
| 107 | imageBuffer: imageBuffer |
||
| 108 | } |
||
| 109 | } |
||
| 110 | } |
||
| 111 | |||
| 112 | module.exports.COMM = { |
||
| 113 | create: (data) => { |
||
| 114 | data = data || {} |
||
| 115 | if(!data.text) { |
||
| 116 | return null |
||
| 117 | } |
||
| 118 | |||
| 119 | return new ID3FrameBuilder("COMM") |
||
| 120 | .appendStaticNumber(0x01, 1) |
||
| 121 | .appendStaticValue(data.language) |
||
| 122 | .appendNullTerminatedValue(data.shortText, 0x01) |
||
| 123 | .appendStaticValue(data.text, null, 0x01) |
||
| 124 | .getBuffer() |
||
| 125 | }, |
||
| 126 | read: (buffer) => { |
||
| 127 | const reader = new ID3FrameReader(buffer, 0) |
||
| 128 | |||
| 129 | return { |
||
| 130 | language: reader.consumeStaticValue('string', 3, 0x00), |
||
| 131 | shortText: reader.consumeNullTerminatedValue('string'), |
||
| 132 | text: reader.consumeStaticValue('string', null) |
||
| 133 | } |
||
| 134 | } |
||
| 135 | } |
||
| 136 | |||
| 137 | module.exports.USLT = { |
||
| 138 | create: (data) => { |
||
| 139 | data = data || {} |
||
| 140 | if(isString(data)) { |
||
| 141 | data = { |
||
| 142 | text: data |
||
| 143 | } |
||
| 144 | } |
||
| 145 | if(!data.text) { |
||
| 146 | return null |
||
| 147 | } |
||
| 148 | |||
| 149 | return new ID3FrameBuilder("USLT") |
||
| 150 | .appendStaticNumber(0x01, 1) |
||
| 151 | .appendStaticValue(data.language) |
||
| 152 | .appendNullTerminatedValue(data.shortText, 0x01) |
||
| 153 | .appendStaticValue(data.text, null, 0x01) |
||
| 154 | .getBuffer() |
||
| 155 | }, |
||
| 156 | read: (buffer) => { |
||
| 157 | const reader = new ID3FrameReader(buffer, 0) |
||
| 158 | |||
| 159 | return { |
||
| 160 | language: reader.consumeStaticValue('string', 3, 0x00), |
||
| 161 | shortText: reader.consumeNullTerminatedValue('string'), |
||
| 162 | text: reader.consumeStaticValue('string', null) |
||
| 163 | } |
||
| 164 | } |
||
| 165 | } |
||
| 166 | |||
| 167 | module.exports.SYLT = { |
||
| 168 | create: (data) => { |
||
| 169 | if(!(data instanceof Array)) { |
||
| 170 | data = [data] |
||
| 171 | } |
||
| 172 | |||
| 173 | const encoding = 1 // 16 bit unicode |
||
| 174 | return Buffer.concat(data.map(lycics => { |
||
| 175 | const frameBuilder = new ID3FrameBuilder("SYLT") |
||
| 176 | .appendStaticNumber(encoding, 1) |
||
| 177 | .appendStaticValue(lycics.language, 3) |
||
| 178 | .appendStaticNumber(lycics.timeStampFormat, 1) |
||
| 179 | .appendStaticNumber(lycics.contentType, 1) |
||
| 180 | .appendNullTerminatedValue(lycics.shortText, encoding) |
||
| 181 | lycics.synchronisedText.forEach(part => { |
||
| 182 | frameBuilder.appendNullTerminatedValue(part.text, encoding) |
||
| 183 | frameBuilder.appendStaticNumber(part.timeStamp, 4) |
||
| 184 | }) |
||
| 185 | return frameBuilder.getBuffer() |
||
| 186 | })) |
||
| 187 | }, |
||
| 188 | read: (buffer) => { |
||
| 189 | const reader = new ID3FrameReader(buffer, 0) |
||
| 190 | |||
| 191 | return { |
||
| 192 | language: reader.consumeStaticValue('string', 3, 0x00), |
||
| 193 | timeStampFormat: reader.consumeStaticValue('number', 1), |
||
| 194 | contentType: reader.consumeStaticValue('number', 1), |
||
| 195 | shortText: reader.consumeNullTerminatedValue('string'), |
||
| 196 | synchronisedText: Array.from((function*() { |
||
| 197 | while(true) { |
||
| 198 | const text = reader.consumeNullTerminatedValue('string') |
||
| 199 | const timeStamp = reader.consumeStaticValue('number', 4) |
||
| 200 | if (text === undefined || timeStamp === undefined) { |
||
| 201 | break |
||
| 202 | } |
||
| 203 | yield {text, timeStamp} |
||
| 204 | } |
||
| 205 | })()) |
||
| 206 | } |
||
| 207 | } |
||
| 208 | } |
||
| 209 | |||
| 210 | module.exports.TXXX = { |
||
| 211 | create: (data) => { |
||
| 212 | if(!(data instanceof Array)) { |
||
| 213 | data = [data] |
||
| 214 | } |
||
| 215 | |||
| 216 | return Buffer.concat(data.map(udt => new ID3FrameBuilder("TXXX") |
||
| 217 | .appendStaticNumber(0x01, 1) |
||
| 218 | .appendNullTerminatedValue(udt.description, 0x01) |
||
| 219 | .appendStaticValue(udt.value, null, 0x01) |
||
| 220 | .getBuffer())) |
||
| 221 | }, |
||
| 222 | read: (buffer) => { |
||
| 223 | const reader = new ID3FrameReader(buffer, 0) |
||
| 224 | |||
| 225 | return { |
||
| 226 | description: reader.consumeNullTerminatedValue('string'), |
||
| 227 | value: reader.consumeStaticValue('string') |
||
| 228 | } |
||
| 229 | } |
||
| 230 | } |
||
| 231 | |||
| 232 | module.exports.POPM = { |
||
| 233 | create: (data) => { |
||
| 234 | const email = data.email |
||
| 235 | let rating = Math.trunc(data.rating) |
||
| 236 | let counter = Math.trunc(data.counter) |
||
| 237 | if(!email) { |
||
| 238 | return null |
||
| 239 | } |
||
| 240 | if(isNaN(rating) || rating < 0 || rating > 255) { |
||
| 241 | rating = 0 |
||
| 242 | } |
||
| 243 | if(isNaN(counter) || counter < 0) { |
||
| 244 | counter = 0 |
||
| 245 | } |
||
| 246 | |||
| 247 | return new ID3FrameBuilder("POPM") |
||
| 248 | .appendNullTerminatedValue(email) |
||
| 249 | .appendStaticNumber(rating, 1) |
||
| 250 | .appendStaticNumber(counter, 4) |
||
| 251 | .getBuffer() |
||
| 252 | }, |
||
| 253 | read: (buffer) => { |
||
| 254 | const reader = new ID3FrameReader(buffer) |
||
| 255 | return { |
||
| 256 | email: reader.consumeNullTerminatedValue('string'), |
||
| 257 | rating: reader.consumeStaticValue('number', 1), |
||
| 258 | counter: reader.consumeStaticValue('number') |
||
| 259 | } |
||
| 260 | } |
||
| 261 | } |
||
| 262 | |||
| 263 | module.exports.PRIV = { |
||
| 264 | create: (data) => { |
||
| 265 | if(!(data instanceof Array)) { |
||
| 266 | data = [data] |
||
| 267 | } |
||
| 268 | |||
| 269 | return Buffer.concat(data.map(priv => new ID3FrameBuilder("PRIV") |
||
| 270 | .appendNullTerminatedValue(priv.ownerIdentifier) |
||
| 271 | .appendStaticValue(priv.data instanceof Buffer ? priv.data : Buffer.from(priv.data, "utf8")) |
||
| 272 | .getBuffer())) |
||
| 273 | }, |
||
| 274 | read: (buffer) => { |
||
| 275 | const reader = new ID3FrameReader(buffer) |
||
| 276 | return { |
||
| 277 | ownerIdentifier: reader.consumeNullTerminatedValue('string'), |
||
| 278 | data: reader.consumeStaticValue() |
||
| 279 | } |
||
| 280 | } |
||
| 281 | } |
||
| 282 | |||
| 283 | module.exports.UFID = { |
||
| 284 | create: (data) => { |
||
| 285 | if (!(data instanceof Array)) { |
||
| 286 | data = [data] |
||
| 287 | } |
||
| 288 | |||
| 289 | return Buffer.concat(data.map(ufid => new ID3FrameBuilder("UFID") |
||
| 290 | .appendNullTerminatedValue(ufid.ownerIdentifier) |
||
| 291 | .appendStaticValue( |
||
| 292 | ufid.identifier instanceof Buffer ? |
||
| 293 | ufid.identifier : Buffer.from(ufid.identifier, "utf8") |
||
| 294 | ) |
||
| 295 | .getBuffer())) |
||
| 296 | }, |
||
| 297 | read: (buffer) => { |
||
| 298 | const reader = new ID3FrameReader(buffer) |
||
| 299 | return { |
||
| 300 | ownerIdentifier: reader.consumeNullTerminatedValue('string'), |
||
| 301 | identifier: reader.consumeStaticValue() |
||
| 302 | } |
||
| 303 | } |
||
| 304 | } |
||
| 305 | |||
| 306 | module.exports.CHAP = { |
||
| 307 | create: (data) => { |
||
| 308 | if (!(data instanceof Array)) { |
||
| 309 | data = [data] |
||
| 310 | } |
||
| 311 | |||
| 312 | return Buffer.concat(data.map(chap => { |
||
| 313 | if (!chap || !chap.elementID || typeof chap.startTimeMs === "undefined" || !chap.endTimeMs) { |
||
| 314 | return null |
||
| 315 | } |
||
| 316 | return new ID3FrameBuilder("CHAP") |
||
| 317 | .appendNullTerminatedValue(chap.elementID) |
||
| 318 | .appendStaticNumber(chap.startTimeMs, 4) |
||
| 319 | .appendStaticNumber(chap.endTimeMs, 4) |
||
| 320 | .appendStaticNumber(chap.startOffsetBytes ? chap.startOffsetBytes : 0xFFFFFFFF, 4) |
||
| 321 | .appendStaticNumber(chap.endOffsetBytes ? chap.endOffsetBytes : 0xFFFFFFFF, 4) |
||
| 322 | .appendStaticValue(ID3Helpers.createBufferFromTags(chap.tags)) |
||
| 323 | .getBuffer() |
||
| 324 | }).filter(chap => chap instanceof Buffer)) |
||
| 325 | }, |
||
| 326 | read: (buffer) => { |
||
| 327 | const reader = new ID3FrameReader(buffer) |
||
| 328 | const chap = { |
||
| 329 | elementID: reader.consumeNullTerminatedValue('string'), |
||
| 330 | startTimeMs: reader.consumeStaticValue('number', 4), |
||
| 331 | endTimeMs: reader.consumeStaticValue('number', 4), |
||
| 332 | startOffsetBytes: reader.consumeStaticValue('number', 4), |
||
| 333 | endOffsetBytes: reader.consumeStaticValue('number', 4), |
||
| 334 | tags: ID3Helpers.getTagsFromID3Body(reader.consumeStaticValue()) |
||
| 335 | } |
||
| 336 | if(chap.startOffsetBytes === 0xFFFFFFFF) { |
||
| 337 | delete chap.startOffsetBytes |
||
| 338 | } |
||
| 339 | if(chap.endOffsetBytes === 0xFFFFFFFF) { |
||
| 340 | delete chap.endOffsetBytes |
||
| 341 | } |
||
| 342 | return chap |
||
| 343 | } |
||
| 344 | } |
||
| 345 | |||
| 346 | module.exports.CTOC = { |
||
| 347 | create: (data) => { |
||
| 348 | if(!(data instanceof Array)) { |
||
| 349 | data = [data] |
||
| 350 | } |
||
| 351 | |||
| 352 | return Buffer.concat(data.map((toc, index) => { |
||
| 353 | if(!toc || !toc.elementID) { |
||
| 354 | return null |
||
| 355 | } |
||
| 356 | if(!(toc.elements instanceof Array)) { |
||
| 357 | toc.elements = [] |
||
| 358 | } |
||
| 359 | |||
| 360 | const ctocFlags = Buffer.alloc(1, 0) |
||
| 361 | if(index === 0) { |
||
| 362 | ctocFlags[0] += 2 |
||
| 363 | } |
||
| 364 | if(toc.isOrdered) { |
||
| 365 | ctocFlags[0] += 1 |
||
| 366 | } |
||
| 367 | |||
| 368 | const builder = new ID3FrameBuilder("CTOC") |
||
| 369 | .appendNullTerminatedValue(toc.elementID) |
||
| 370 | .appendStaticValue(ctocFlags, 1) |
||
| 371 | .appendStaticNumber(toc.elements.length, 1) |
||
| 372 | toc.elements.forEach((el) => { |
||
| 373 | builder.appendNullTerminatedValue(el) |
||
| 374 | }) |
||
| 375 | if(toc.tags) { |
||
| 376 | builder.appendStaticValue(ID3Helpers.createBufferFromTags(toc.tags)) |
||
| 377 | } |
||
| 378 | return builder.getBuffer() |
||
| 379 | }).filter((toc) => toc instanceof Buffer)) |
||
| 380 | }, |
||
| 381 | read: (buffer) => { |
||
| 382 | const reader = new ID3FrameReader(buffer) |
||
| 383 | const elementID = reader.consumeNullTerminatedValue('string') |
||
| 384 | const flags = reader.consumeStaticValue('number', 1) |
||
| 385 | const entries = reader.consumeStaticValue('number', 1) |
||
| 386 | const elements = [] |
||
| 387 | for(let i = 0; i < entries; i++) { |
||
| 388 | elements.push(reader.consumeNullTerminatedValue('string')) |
||
| 389 | } |
||
| 390 | const tags = ID3Helpers.getTagsFromID3Body(reader.consumeStaticValue()) |
||
| 391 | |||
| 392 | return { |
||
| 393 | elementID, |
||
| 394 | isOrdered: !!(flags & 0x01 === 0x01), |
||
| 395 | elements, |
||
| 396 | tags |
||
| 397 | } |
||
| 398 | } |
||
| 399 | } |
||
| 400 | |||
| 401 | module.exports.WXXX = { |
||
| 402 | create: (data) => { |
||
| 403 | if(!(data instanceof Array)) { |
||
| 404 | data = [data] |
||
| 405 | } |
||
| 406 | |||
| 407 | return Buffer.concat(data.map((udu) => { |
||
| 408 | return new ID3FrameBuilder("WXXX") |
||
| 409 | .appendStaticNumber(0x01, 1) |
||
| 410 | .appendNullTerminatedValue(udu.description, 0x01) |
||
| 411 | .appendStaticValue(udu.url, null) |
||
| 412 | .getBuffer() |
||
| 413 | })) |
||
| 414 | }, |
||
| 415 | read: (buffer) => { |
||
| 416 | const reader = new ID3FrameReader(buffer, 0) |
||
| 417 | |||
| 418 | return { |
||
| 419 | description: reader.consumeNullTerminatedValue('string'), |
||
| 420 | url: reader.consumeStaticValue('string', null, 0x00) |
||
| 421 | } |
||
| 422 | } |
||
| 423 | } |
||
| 424 | |||
| 425 | module.exports.ETCO = { |
||
| 426 | create: (data) => { |
||
| 427 | const builder = new ID3FrameBuilder("ETCO") |
||
| 428 | .appendStaticNumber(data.timeStampFormat, 1) |
||
| 429 | data.keyEvents.forEach((keyEvent) => { |
||
| 430 | builder |
||
| 431 | .appendStaticNumber(keyEvent.type, 1) |
||
| 432 | .appendStaticNumber(keyEvent.timeStamp, 4) |
||
| 433 | }) |
||
| 434 | |||
| 435 | return builder.getBuffer() |
||
| 436 | }, |
||
| 437 | read: (buffer) => { |
||
| 438 | const reader = new ID3FrameReader(buffer) |
||
| 439 | |||
| 440 | return { |
||
| 441 | timeStampFormat: reader.consumeStaticValue('number', 1), |
||
| 442 | keyEvents: Array.from((function*() { |
||
| 443 | while(true) { |
||
| 444 | const type = reader.consumeStaticValue('number', 1) |
||
| 445 | const timeStamp = reader.consumeStaticValue('number', 4) |
||
| 446 | if (type === undefined || timeStamp === undefined) { |
||
| 447 | break |
||
| 448 | } |
||
| 449 | yield {type, timeStamp} |
||
| 450 | } |
||
| 451 | })()) |
||
| 452 | } |
||
| 453 | } |
||
| 454 | } |
||
| 455 | |||
| 456 | module.exports.COMR = { |
||
| 457 | create: (data) => { |
||
| 458 | if(!(data instanceof Array)) { |
||
| 459 | data = [data] |
||
| 460 | } |
||
| 461 | |||
| 462 | return Buffer.concat(data.map(comr => { |
||
| 463 | const prices = comr.prices || {} |
||
| 464 | const builder = new ID3FrameBuilder("COMR") |
||
| 465 | |||
| 466 | // Text encoding |
||
| 467 | builder.appendStaticNumber(0x01, 1) |
||
| 468 | // Price string |
||
| 469 | const priceString = Object.entries(prices).map((price) => { |
||
| 470 | return price[0].substring(0, 3) + price[1].toString() |
||
| 471 | }).join('/') |
||
| 472 | builder.appendNullTerminatedValue(priceString, 0x00) |
||
| 473 | // Valid until |
||
| 474 | builder.appendStaticValue( |
||
| 475 | comr.validUntil.year.toString().padStart(4, '0').substring(0, 4) + |
||
| 476 | comr.validUntil.month.toString().padStart(2, '0').substring(0, 2) + |
||
| 477 | comr.validUntil.day.toString().padStart(2, '0').substring(0, 2), |
||
| 478 | 8, 0x00 |
||
| 479 | ) |
||
| 480 | // Contact URL |
||
| 481 | builder.appendNullTerminatedValue(comr.contactUrl, 0x00) |
||
| 482 | // Received as |
||
| 483 | builder.appendStaticNumber(comr.receivedAs, 1) |
||
| 484 | // Name of seller |
||
| 485 | builder.appendNullTerminatedValue(comr.nameOfSeller, 0x01) |
||
| 486 | // Description |
||
| 487 | builder.appendNullTerminatedValue(comr.description, 0x01) |
||
| 488 | // Seller logo |
||
| 489 | if(comr.sellerLogo) { |
||
| 490 | let picture = comr.sellerLogo.picture |
||
| 491 | if(typeof comr.sellerLogo.picture === 'string' || comr.sellerLogo.picture instanceof String) { |
||
| 492 | picture = fs.readFileSync(comr.sellerLogo.picture) |
||
| 493 | } |
||
| 494 | let mimeType = comr.sellerLogo.mimeType || ID3Util.getPictureMimeTypeFromBuffer(picture) |
||
| 495 | // Only image/png and image/jpeg allowed |
||
| 496 | if(mimeType !== 'image/png' && 'image/jpeg') { |
||
| 497 | mimeType = 'image/' |
||
| 498 | } |
||
| 499 | |||
| 500 | builder.appendNullTerminatedValue(mimeType ? mimeType : '', 0x00) |
||
| 501 | builder.appendStaticValue(picture) |
||
| 502 | } |
||
| 503 | return builder.getBuffer() |
||
| 504 | })) |
||
| 505 | }, |
||
| 506 | read: (buffer) => { |
||
| 507 | const reader = new ID3FrameReader(buffer, 0) |
||
| 508 | |||
| 509 | const tag = {} |
||
| 510 | |||
| 511 | // Price string |
||
| 512 | const priceStrings = reader.consumeNullTerminatedValue('string', 0x00) |
||
| 513 | .split('/') |
||
| 514 | .filter((price) => price.length > 3) |
||
| 515 | tag.prices = {} |
||
| 516 | for(const price of priceStrings) { |
||
| 517 | tag.prices[price.substring(0, 3)] = price.substring(3) |
||
| 518 | } |
||
| 519 | // Valid until |
||
| 520 | const validUntilString = reader.consumeStaticValue('string', 8, 0x00) |
||
| 521 | tag.validUntil = { year: 0, month: 0, day: 0 } |
||
| 522 | if(/^\d+$/.test(validUntilString)) { |
||
| 523 | tag.validUntil.year = parseInt(validUntilString.substring(0, 4)) |
||
| 524 | tag.validUntil.month = parseInt(validUntilString.substring(4, 6)) |
||
| 525 | tag.validUntil.day = parseInt(validUntilString.substring(6)) |
||
| 526 | } |
||
| 527 | // Contact URL |
||
| 528 | tag.contactUrl = reader.consumeNullTerminatedValue('string', 0x00) |
||
| 529 | // Received as |
||
| 530 | tag.receivedAs = reader.consumeStaticValue('number', 1) |
||
| 531 | // Name of seller |
||
| 532 | tag.nameOfSeller = reader.consumeNullTerminatedValue('string') |
||
| 533 | // Description |
||
| 534 | tag.description = reader.consumeNullTerminatedValue('string') |
||
| 535 | // Seller logo |
||
| 536 | const mimeType = reader.consumeNullTerminatedValue('string', 0x00) |
||
| 537 | const picture = reader.consumeStaticValue('buffer') |
||
| 538 | if(picture && picture.length > 0) { |
||
| 539 | tag.sellerLogo = { |
||
| 540 | mimeType, |
||
| 541 | picture |
||
| 542 | } |
||
| 543 | } |
||
| 544 | |||
| 545 | return tag |
||
| 546 | } |
||
| 547 | } |
||
| 548 | |||
| 549 | module.exports.GEOB = { |
||
| 550 | create: (data) => { |
||
| 551 | if (!(data instanceof Array)) { |
||
| 552 | data = [data] |
||
| 553 | } |
||
| 554 | |||
| 555 | const uniqueSet = new Set() |
||
| 556 | data.forEach(item => { |
||
| 557 | if(item.encapsulatedObject == null || item.encapsulatedObject.length === 0) { |
||
|
0 ignored issues
–
show
Best Practice
introduced
by
Loading history...
|
|||
| 558 | throw new Error("encapsulatedObject is required in GEOB frames") |
||
| 559 | } |
||
| 560 | |||
| 561 | if(item.contentDescription == null || item.contentDescription === "") { |
||
|
0 ignored issues
–
show
|
|||
| 562 | throw new Error("contentDescription is required for GEOB frames") |
||
| 563 | } |
||
| 564 | |||
| 565 | if (uniqueSet.has(item.contentDescription)) { |
||
| 566 | throw new Error(`duplicate GEOB contentDescription found: ${item.contentDescription}`) |
||
| 567 | } |
||
| 568 | uniqueSet.add(item.contentDescription) |
||
| 569 | }) |
||
| 570 | |||
| 571 | const encoding = 0x01 // UTF-16 BOM |
||
| 572 | return Buffer.concat(data.map((geob) => { |
||
| 573 | return new ID3FrameBuilder("GEOB") |
||
| 574 | .appendStaticNumber(encoding) |
||
| 575 | .appendNullTerminatedValue(geob.mimeType ? geob.mimeType : '') |
||
| 576 | .appendNullTerminatedValue(geob.filename ? geob.filename : '', encoding) |
||
| 577 | .appendNullTerminatedValue(geob.contentDescription, encoding) |
||
| 578 | .appendStaticValue(geob.encapsulatedObject) |
||
| 579 | .getBuffer() |
||
| 580 | })) |
||
| 581 | }, |
||
| 582 | |||
| 583 | read: (buffer) => { |
||
| 584 | const reader = new ID3FrameReader(buffer, 0) |
||
| 585 | return { |
||
| 586 | mimeType: reader.consumeNullTerminatedValue('string', 0), |
||
| 587 | filename: reader.consumeNullTerminatedValue('string'), |
||
| 588 | contentDescription: reader.consumeNullTerminatedValue('string'), |
||
| 589 | encapsulatedObject: reader.consumeStaticValue('buffer') |
||
| 590 | } |
||
| 591 | } |
||
| 592 | } |
||
| 593 |