新增FLAC格式音乐标签信息写入
parent
bc7c3968b6
commit
83f0ca918a
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "lx-music-desktop",
|
||||
"version": "0.17.0",
|
||||
"version": "0.18.0",
|
||||
"description": "一个免费的音乐下载助手",
|
||||
"main": "./dist/electron/main.js",
|
||||
"productName": "lx-music-desktop",
|
||||
|
@ -141,13 +141,13 @@
|
|||
},
|
||||
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.7",
|
||||
"@babel/core": "^7.9.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/polyfill": "^7.8.7",
|
||||
"@babel/preset-env": "^7.8.7",
|
||||
"@babel/preset-env": "^7.9.0",
|
||||
"autoprefixer": "^9.7.4",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-minify-webpack-plugin": "^0.3.1",
|
||||
"babel-preset-minify": "^0.5.1",
|
||||
"browserslist": "^4.10.0",
|
||||
|
@ -164,9 +164,9 @@
|
|||
"electron-builder": "^22.4.1",
|
||||
"electron-debug": "^3.0.1",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"electron-to-chromium": "^1.3.379",
|
||||
"electron-to-chromium": "^1.3.380",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-standard": "^14.1.0",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-formatter-friendly": "^7.0.0",
|
||||
"eslint-loader": "^3.0.3",
|
||||
"eslint-plugin-html": "^6.0.0",
|
||||
|
@ -209,11 +209,10 @@
|
|||
"electron-log": "^4.1.0",
|
||||
"electron-store": "^5.1.1",
|
||||
"electron-updater": "^4.2.5",
|
||||
"flac-metadata": "^0.1.1",
|
||||
"js-htmlencode": "^0.3.0",
|
||||
"lrc-file-parser": "^1.0.1",
|
||||
"needle": "^2.3.3",
|
||||
"node-id3": "^0.1.14",
|
||||
"node-id3": "^0.1.15",
|
||||
"request": "^2.88.2",
|
||||
"vue": "^2.6.11",
|
||||
"vue-electron": "^1.0.6",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
### 新增
|
||||
|
||||
- 新增FLAC格式音乐标签信息写入
|
||||
|
||||
### 优化
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
// https://github.com/claus/flac-metadata
|
||||
|
||||
module.exports.Processor = require('./lib/Processor')
|
||||
|
||||
module.exports.data = {
|
||||
MetaDataBlock: require('./lib/data/MetaDataBlock'),
|
||||
MetaDataBlockStreamInfo: require('./lib/data/MetaDataBlockStreamInfo'),
|
||||
MetaDataBlockVorbisComment: require('./lib/data/MetaDataBlockVorbisComment'),
|
||||
MetaDataBlockPicture: require('./lib/data/MetaDataBlockPicture'),
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
const Transform = require('stream').Transform
|
||||
|
||||
const MetaDataBlock = require('./data/MetaDataBlock')
|
||||
const MetaDataBlockStreamInfo = require('./data/MetaDataBlockStreamInfo')
|
||||
const MetaDataBlockVorbisComment = require('./data/MetaDataBlockVorbisComment')
|
||||
const MetaDataBlockPicture = require('./data/MetaDataBlockPicture')
|
||||
|
||||
const STATE_IDLE = 0
|
||||
const STATE_MARKER = 1
|
||||
const STATE_MDB_HEADER = 2
|
||||
const STATE_MDB = 3
|
||||
const STATE_PASS_THROUGH = 4
|
||||
|
||||
class Processor extends Transform {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
// MDB types
|
||||
this.MDB_TYPE_STREAMINFO = 0
|
||||
this.MDB_TYPE_PADDING = 1
|
||||
this.MDB_TYPE_APPLICATION = 2
|
||||
this.MDB_TYPE_SEEKTABLE = 3
|
||||
this.MDB_TYPE_VORBIS_COMMENT = 4
|
||||
this.MDB_TYPE_CUESHEET = 5
|
||||
this.MDB_TYPE_PICTURE = 6
|
||||
this.MDB_TYPE_INVALID = 127
|
||||
|
||||
this.state = STATE_IDLE
|
||||
|
||||
this.isFlac = false
|
||||
|
||||
this.buf = null
|
||||
this.bufPos = 0
|
||||
|
||||
this.mdb = null
|
||||
this.mdbLen = 0
|
||||
this.mdbLast = false
|
||||
this.mdbPush = false
|
||||
this.mdbLastWritten = false
|
||||
|
||||
this.parseMetaDataBlocks = false
|
||||
|
||||
if (!(this instanceof Processor)) return new Processor(options)
|
||||
if (options && !!options.parseMetaDataBlocks) { this.parseMetaDataBlocks = true }
|
||||
}
|
||||
|
||||
_transform(chunk, enc, done) {
|
||||
let chunkPos = 0
|
||||
let chunkLen = chunk.length
|
||||
let isChunkProcessed = false
|
||||
let _this = this
|
||||
|
||||
function _safePush(minCapacity, persist, validate) {
|
||||
let slice
|
||||
let chunkAvailable = chunkLen - chunkPos
|
||||
let isDone = (chunkAvailable + this.bufPos >= minCapacity)
|
||||
validate = (typeof validate === 'function') ? validate : function() { return true }
|
||||
if (isDone) {
|
||||
// Enough data available
|
||||
if (persist) {
|
||||
// Persist the entire block so it can be parsed
|
||||
if (this.bufPos > 0) {
|
||||
// Part of this block's data is in backup buffer, copy rest over
|
||||
chunk.copy(this.buf, this.bufPos, chunkPos, chunkPos + minCapacity - this.bufPos)
|
||||
slice = this.buf.slice(0, minCapacity)
|
||||
} else {
|
||||
// Entire block fits in current chunk
|
||||
slice = chunk.slice(chunkPos, chunkPos + minCapacity)
|
||||
}
|
||||
} else {
|
||||
slice = chunk.slice(chunkPos, chunkPos + minCapacity - this.bufPos)
|
||||
}
|
||||
// Push block after validation
|
||||
validate(slice, isDone) && _this.push(slice)
|
||||
chunkPos += minCapacity - this.bufPos
|
||||
this.bufPos = 0
|
||||
this.buf = null
|
||||
} else {
|
||||
// Not enough data available
|
||||
if (persist) {
|
||||
// Copy/append incomplete block to backup buffer
|
||||
this.buf = this.buf || Buffer.alloc(minCapacity)
|
||||
chunk.copy(this.buf, this.bufPos, chunkPos, chunkLen)
|
||||
} else {
|
||||
// Push incomplete block after validation
|
||||
slice = chunk.slice(chunkPos, chunkLen)
|
||||
validate(slice, isDone) && _this.push(slice)
|
||||
}
|
||||
this.bufPos += chunkLen - chunkPos
|
||||
}
|
||||
return isDone
|
||||
};
|
||||
let safePush = _safePush.bind(this)
|
||||
|
||||
while (!isChunkProcessed) {
|
||||
switch (this.state) {
|
||||
case STATE_IDLE:
|
||||
this.state = STATE_MARKER
|
||||
break
|
||||
case STATE_MARKER:
|
||||
if (safePush(4, true, this._validateMarker.bind(this))) {
|
||||
this.state = this.isFlac ? STATE_MDB_HEADER : STATE_PASS_THROUGH
|
||||
} else {
|
||||
isChunkProcessed = true
|
||||
}
|
||||
break
|
||||
case STATE_MDB_HEADER:
|
||||
if (safePush(4, true, this._validateMDBHeader.bind(this))) {
|
||||
this.state = STATE_MDB
|
||||
} else {
|
||||
isChunkProcessed = true
|
||||
}
|
||||
break
|
||||
case STATE_MDB:
|
||||
if (safePush(this.mdbLen, this.parseMetaDataBlocks, this._validateMDB.bind(this))) {
|
||||
if (this.mdb.isLast) {
|
||||
// This MDB has the isLast flag set to true.
|
||||
// Ignore all following MDBs.
|
||||
this.mdbLastWritten = true
|
||||
}
|
||||
this.emit('postprocess', this.mdb)
|
||||
this.state = this.mdbLast ? STATE_PASS_THROUGH : STATE_MDB_HEADER
|
||||
} else {
|
||||
isChunkProcessed = true
|
||||
}
|
||||
break
|
||||
case STATE_PASS_THROUGH:
|
||||
safePush(chunkLen - chunkPos, false)
|
||||
isChunkProcessed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
done()
|
||||
}
|
||||
|
||||
_validateMarker(slice, isDone) {
|
||||
this.isFlac = (slice.toString('utf8', 0) === 'fLaC')
|
||||
// TODO: completely bail out if file is not a FLAC?
|
||||
return true
|
||||
}
|
||||
|
||||
_validateMDBHeader(slice, isDone) {
|
||||
// Parse MDB header
|
||||
let header = slice.readUInt32BE(0)
|
||||
let type = (header >>> 24) & 0x7f
|
||||
this.mdbLast = (((header >>> 24) & 0x80) !== 0)
|
||||
this.mdbLen = header & 0xffffff
|
||||
|
||||
// Create appropriate MDB object
|
||||
// (data is injected later in _validateMDB, if parseMetaDataBlocks option is set to true)
|
||||
switch (type) {
|
||||
case Processor.MDB_TYPE_STREAMINFO:
|
||||
this.mdb = new MetaDataBlockStreamInfo(this.mdbLast)
|
||||
break
|
||||
case Processor.MDB_TYPE_VORBIS_COMMENT:
|
||||
this.mdb = new MetaDataBlockVorbisComment(this.mdbLast)
|
||||
break
|
||||
case Processor.MDB_TYPE_PICTURE:
|
||||
this.mdb = new MetaDataBlockPicture(this.mdbLast)
|
||||
break
|
||||
case Processor.MDB_TYPE_PADDING:
|
||||
case Processor.MDB_TYPE_APPLICATION:
|
||||
case Processor.MDB_TYPE_SEEKTABLE:
|
||||
case Processor.MDB_TYPE_CUESHEET:
|
||||
case Processor.MDB_TYPE_INVALID:
|
||||
default:
|
||||
this.mdb = new MetaDataBlock(this.mdbLast, type)
|
||||
break
|
||||
}
|
||||
|
||||
this.emit('preprocess', this.mdb)
|
||||
|
||||
if (this.mdbLastWritten) {
|
||||
// A previous MDB had the isLast flag set to true.
|
||||
// Ignore all following MDBs.
|
||||
this.mdb.remove()
|
||||
} else {
|
||||
// The consumer may change the MDB's isLast flag in the preprocess handler.
|
||||
// Here that flag is updated in the MDB header.
|
||||
if (this.mdbLast !== this.mdb.isLast) {
|
||||
if (this.mdb.isLast) {
|
||||
header |= 0x80000000
|
||||
} else {
|
||||
header &= 0x7fffffff
|
||||
}
|
||||
slice.writeUInt32BE(header >>> 0, 0)
|
||||
}
|
||||
}
|
||||
this.mdbPush = !this.mdb.removed
|
||||
return this.mdbPush
|
||||
}
|
||||
|
||||
_validateMDB(slice, isDone) {
|
||||
// Parse the MDB if parseMetaDataBlocks option is set to true
|
||||
if (this.parseMetaDataBlocks && isDone) {
|
||||
this.mdb.parse(slice)
|
||||
}
|
||||
return this.mdbPush
|
||||
}
|
||||
|
||||
_flush(done) {
|
||||
// All chunks have been processed
|
||||
// Clean up
|
||||
this.state = STATE_IDLE
|
||||
this.mdbLastWritten = false
|
||||
this.isFlac = false
|
||||
this.bufPos = 0
|
||||
this.buf = null
|
||||
this.mdb = null
|
||||
done()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Processor
|
|
@ -0,0 +1,25 @@
|
|||
class MetaDataBlock {
|
||||
constructor(isLast, type) {
|
||||
this.isLast = isLast
|
||||
this.type = type
|
||||
this.error = null
|
||||
this.hasData = false
|
||||
this.removed = false
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.removed = true
|
||||
}
|
||||
|
||||
parse(buffer) {
|
||||
}
|
||||
|
||||
toString() {
|
||||
let str = '[MetaDataBlock]'
|
||||
str += ' type: ' + this.type
|
||||
str += ', isLast: ' + this.isLast
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MetaDataBlock
|
|
@ -0,0 +1,130 @@
|
|||
const MetaDataBlock = require('./MetaDataBlock')
|
||||
|
||||
class MetaDataBlockPicture extends MetaDataBlock {
|
||||
constructor(isLast) {
|
||||
super(isLast, 6)
|
||||
|
||||
this.pictureType = 0
|
||||
this.mimeType = ''
|
||||
this.description = ''
|
||||
this.width = 0
|
||||
this.height = 0
|
||||
this.bitsPerPixel = 0
|
||||
this.colors = 0
|
||||
this.pictureData = null
|
||||
}
|
||||
|
||||
static create(isLast, pictureType, mimeType, description, width, height, bitsPerPixel, colors, pictureData) {
|
||||
let mdb = new MetaDataBlockPicture(isLast)
|
||||
mdb.pictureType = pictureType
|
||||
mdb.mimeType = mimeType
|
||||
mdb.description = description
|
||||
mdb.width = width
|
||||
mdb.height = height
|
||||
mdb.bitsPerPixel = bitsPerPixel
|
||||
mdb.colors = colors
|
||||
mdb.pictureData = pictureData
|
||||
mdb.hasData = true
|
||||
return mdb
|
||||
}
|
||||
|
||||
|
||||
parse(buffer) {
|
||||
try {
|
||||
let pos = 0
|
||||
|
||||
this.pictureType = buffer.readUInt32BE(pos)
|
||||
pos += 4
|
||||
|
||||
let mimeTypeLength = buffer.readUInt32BE(pos)
|
||||
this.mimeType = buffer.toString('utf8', pos + 4, pos + 4 + mimeTypeLength)
|
||||
pos += 4 + mimeTypeLength
|
||||
|
||||
let descriptionLength = buffer.readUInt32BE(pos)
|
||||
this.description = buffer.toString('utf8', pos + 4, pos + 4 + descriptionLength)
|
||||
pos += 4 + descriptionLength
|
||||
|
||||
this.width = buffer.readUInt32BE(pos)
|
||||
this.height = buffer.readUInt32BE(pos + 4)
|
||||
this.bitsPerPixel = buffer.readUInt32BE(pos + 8)
|
||||
this.colors = buffer.readUInt32BE(pos + 12)
|
||||
pos += 16
|
||||
|
||||
let pictureDataLength = buffer.readUInt32BE(pos)
|
||||
this.pictureData = Buffer.alloc(pictureDataLength)
|
||||
buffer.copy(this.pictureData, 0, pos + 4, pictureDataLength)
|
||||
|
||||
this.hasData = true
|
||||
} catch (e) {
|
||||
this.error = e
|
||||
this.hasData = false
|
||||
}
|
||||
}
|
||||
|
||||
publish() {
|
||||
let pos = 0
|
||||
let size = this.getSize()
|
||||
let buffer = Buffer.alloc(4 + size)
|
||||
|
||||
let header = size
|
||||
header |= (this.type << 24)
|
||||
header |= (this.isLast ? 0x80000000 : 0)
|
||||
buffer.writeUInt32BE(header >>> 0, pos)
|
||||
pos += 4
|
||||
|
||||
buffer.writeUInt32BE(this.pictureType, pos)
|
||||
pos += 4
|
||||
|
||||
let mimeTypeLen = Buffer.byteLength(this.mimeType)
|
||||
buffer.writeUInt32BE(mimeTypeLen, pos)
|
||||
buffer.write(this.mimeType, pos + 4)
|
||||
pos += 4 + mimeTypeLen
|
||||
|
||||
let descriptionLen = Buffer.byteLength(this.description)
|
||||
buffer.writeUInt32BE(descriptionLen, pos)
|
||||
buffer.write(this.description, pos + 4)
|
||||
pos += 4 + descriptionLen
|
||||
|
||||
buffer.writeUInt32BE(this.width, pos)
|
||||
buffer.writeUInt32BE(this.height, pos + 4)
|
||||
buffer.writeUInt32BE(this.bitsPerPixel, pos + 8)
|
||||
buffer.writeUInt32BE(this.colors, pos + 12)
|
||||
pos += 16
|
||||
|
||||
buffer.writeUInt32BE(this.pictureData.length, pos)
|
||||
this.pictureData.copy(buffer, pos + 4)
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
getSize() {
|
||||
let size = 4
|
||||
size += 4 + Buffer.byteLength(this.mimeType)
|
||||
size += 4 + Buffer.byteLength(this.description)
|
||||
size += 16
|
||||
size += 4 + this.pictureData.length
|
||||
return size
|
||||
}
|
||||
|
||||
toString() {
|
||||
let str = '[MetaDataBlockPicture]'
|
||||
str += ' type: ' + this.type
|
||||
str += ', isLast: ' + this.isLast
|
||||
if (this.error) {
|
||||
str += '\n ERROR: ' + this.error
|
||||
}
|
||||
if (this.hasData) {
|
||||
str += '\n pictureType: ' + this.pictureType
|
||||
str += '\n mimeType: ' + this.mimeType
|
||||
str += '\n description: ' + this.description
|
||||
str += '\n width: ' + this.width
|
||||
str += '\n height: ' + this.height
|
||||
str += '\n bitsPerPixel: ' + this.bitsPerPixel
|
||||
str += '\n colors: ' + this.colors
|
||||
str += '\n pictureData: ' + (this.pictureData ? this.pictureData.length : '<null>')
|
||||
}
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MetaDataBlockPicture
|
|
@ -0,0 +1,84 @@
|
|||
const MetaDataBlock = require('./MetaDataBlock')
|
||||
|
||||
function pad(n, width) {
|
||||
n = '' + n
|
||||
return (n.length >= width) ? n : new Array(width - n.length + 1).join('0') + n
|
||||
}
|
||||
|
||||
class MetaDataBlockStreamInfo extends MetaDataBlock {
|
||||
constructor(isLast) {
|
||||
super(isLast, 0)
|
||||
|
||||
this.minBlockSize = 0
|
||||
this.maxBlockSize = 0
|
||||
this.minFrameSize = 0
|
||||
this.maxFrameSize = 0
|
||||
this.sampleRate = 0
|
||||
this.channels = 0
|
||||
this.bitsPerSample = 0
|
||||
this.samples = 0
|
||||
this.checksum = null
|
||||
this.duration = 0
|
||||
this.durationStr = '0:00.000'
|
||||
}
|
||||
|
||||
remove() {
|
||||
console.error("WARNING: Can't remove StreamInfo block!")
|
||||
}
|
||||
|
||||
parse(buffer) {
|
||||
try {
|
||||
let pos = 0
|
||||
|
||||
this.minBlockSize = buffer.readUInt16BE(pos)
|
||||
this.maxBlockSize = buffer.readUInt16BE(pos + 2)
|
||||
this.minFrameSize = (buffer.readUInt8(pos + 4) << 16) | buffer.readUInt16BE(pos + 5)
|
||||
this.maxFrameSize = (buffer.readUInt8(pos + 7) << 16) | buffer.readUInt16BE(pos + 8)
|
||||
|
||||
let tmp = buffer.readUInt32BE(pos + 10)
|
||||
this.sampleRate = tmp >>> 12
|
||||
this.channels = (tmp >>> 9) & 0x07
|
||||
this.bitsPerSample = (tmp >>> 4) & 0x1f
|
||||
this.samples = +((tmp & 0x0f) << 4) + buffer.readUInt32BE(pos + 14)
|
||||
|
||||
this.checksum = Buffer.alloc(16)
|
||||
buffer.copy(this.checksum, 0, 18, 34)
|
||||
|
||||
this.duration = this.samples / this.sampleRate
|
||||
|
||||
let minutes = '' + Math.floor(this.duration / 60)
|
||||
let seconds = pad(Math.floor(this.duration % 60), 2)
|
||||
let milliseconds = pad(Math.round(((this.duration % 60) - Math.floor(this.duration % 60)) * 1000), 3)
|
||||
this.durationStr = minutes + ':' + seconds + '.' + milliseconds
|
||||
|
||||
this.hasData = true
|
||||
} catch (e) {
|
||||
this.error = e
|
||||
this.hasData = false
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
let str = '[MetaDataBlockStreamInfo]'
|
||||
str += ' type: ' + this.type
|
||||
str += ', isLast: ' + this.isLast
|
||||
if (this.error) {
|
||||
str += '\n ERROR: ' + this.error
|
||||
}
|
||||
if (this.hasData) {
|
||||
str += '\n minBlockSize: ' + this.minBlockSize
|
||||
str += '\n maxBlockSize: ' + this.maxBlockSize
|
||||
str += '\n minFrameSize: ' + this.minFrameSize
|
||||
str += '\n maxFrameSize: ' + this.maxFrameSize
|
||||
str += '\n samples: ' + this.samples
|
||||
str += '\n sampleRate: ' + this.sampleRate
|
||||
str += '\n channels: ' + (this.channels + 1)
|
||||
str += '\n bitsPerSample: ' + (this.bitsPerSample + 1)
|
||||
str += '\n duration: ' + this.durationStr
|
||||
str += '\n checksum: ' + (this.checksum ? this.checksum.toString('hex') : '<null>')
|
||||
}
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MetaDataBlockStreamInfo
|
|
@ -0,0 +1,105 @@
|
|||
const MetaDataBlock = require('./MetaDataBlock')
|
||||
|
||||
class MetaDataBlockVorbisComment extends MetaDataBlock {
|
||||
constructor(isLast) {
|
||||
super(isLast, 4)
|
||||
|
||||
this.vendor = ''
|
||||
this.comments = []
|
||||
}
|
||||
|
||||
static create(isLast, vendor, comments) {
|
||||
let mdb = new MetaDataBlockVorbisComment(isLast)
|
||||
mdb.vendor = vendor
|
||||
mdb.comments = comments
|
||||
mdb.hasData = true
|
||||
return mdb
|
||||
}
|
||||
|
||||
parse(buffer) {
|
||||
try {
|
||||
let pos = 0
|
||||
|
||||
let vendorLen = buffer.readUInt32LE(pos)
|
||||
let vendor = buffer.toString('utf8', pos + 4, pos + 4 + vendorLen)
|
||||
this.vendor = vendor
|
||||
pos += 4 + vendorLen
|
||||
|
||||
let commentCount = buffer.readUInt32LE(pos)
|
||||
pos += 4
|
||||
|
||||
while (commentCount-- > 0) {
|
||||
let commentLen = buffer.readUInt32LE(pos)
|
||||
let comment = buffer.toString('utf8', pos + 4, pos + 4 + commentLen)
|
||||
this.comments.push(comment)
|
||||
pos += 4 + commentLen
|
||||
}
|
||||
|
||||
this.hasData = true
|
||||
} catch (e) {
|
||||
this.error = e
|
||||
this.hasData = false
|
||||
}
|
||||
}
|
||||
|
||||
publish() {
|
||||
let pos = 0
|
||||
let size = this.getSize()
|
||||
let buffer = Buffer.alloc(4 + size)
|
||||
|
||||
let header = size
|
||||
header |= (this.type << 24)
|
||||
header |= (this.isLast ? 0x80000000 : 0)
|
||||
buffer.writeUInt32BE(header >>> 0, pos)
|
||||
pos += 4
|
||||
let vendorLen = Buffer.byteLength(this.vendor)
|
||||
buffer.writeUInt32LE(vendorLen, pos)
|
||||
buffer.write(this.vendor, pos + 4)
|
||||
pos += 4 + vendorLen
|
||||
|
||||
let commentCount = this.comments.length
|
||||
buffer.writeUInt32LE(commentCount, pos)
|
||||
pos += 4
|
||||
|
||||
for (let i = 0; i < commentCount; i++) {
|
||||
let comment = this.comments[i]
|
||||
let commentLen = Buffer.byteLength(comment)
|
||||
buffer.writeUInt32LE(commentLen, pos)
|
||||
buffer.write(comment, pos + 4)
|
||||
pos += 4 + commentLen
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
getSize() {
|
||||
let size = 8 + Buffer.byteLength(this.vendor)
|
||||
for (let i = 0; i < this.comments.length; i++) {
|
||||
size += 4 + Buffer.byteLength(this.comments[i])
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
toString() {
|
||||
let str = '[MetaDataBlockVorbisComment]'
|
||||
str += ' type: ' + this.type
|
||||
str += ', isLast: ' + this.isLast
|
||||
if (this.error) {
|
||||
str += '\n ERROR: ' + this.error
|
||||
}
|
||||
if (this.hasData) {
|
||||
str += '\n vendor: ' + this.vendor
|
||||
if (this.comments.length) {
|
||||
str += '\n comments:'
|
||||
for (let i = 0; i < this.comments.length; i++) {
|
||||
str += '\n ' + this.comments[i].split('=').join(': ')
|
||||
}
|
||||
} else {
|
||||
str += '\n comments: none'
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MetaDataBlockVorbisComment
|
|
@ -1,32 +1,32 @@
|
|||
const fs = require('fs')
|
||||
const flac = require('flac-metadata')
|
||||
const { Processor: FlacProcessor, data: { MetaDataBlockVorbisComment: FlacComment } } = require('./flac-metadata')
|
||||
const vendor = 'reference libFLAC 1.2.1 20070917'
|
||||
|
||||
module.exports = (filenPath, meta) => {
|
||||
const reader = fs.createReadStream(filenPath)
|
||||
const tempPath = filenPath + '.lxmtemp'
|
||||
const writer = fs.createWriteStream(tempPath)
|
||||
const processor = new flac.Processor()
|
||||
const flacProcessor = new FlacProcessor()
|
||||
if (meta.APIC) delete meta.APIC
|
||||
|
||||
const comments = []
|
||||
for (const key in meta) {
|
||||
comments.push(`${key.toUpperCase()}=${meta[key]}`)
|
||||
}
|
||||
const vendor = 'lx-music-desktop'
|
||||
|
||||
processor.on('preprocess', function(mdb) {
|
||||
let isInjected = false
|
||||
flacProcessor.on('preprocess', function(mdb) {
|
||||
// Remove existing VORBIS_COMMENT block, if any.
|
||||
if (mdb.type === flac.Processor.MDB_TYPE_VORBIS_COMMENT) {
|
||||
mdb.remove()
|
||||
}
|
||||
if (mdb.type === flacProcessor.MDB_TYPE_VORBIS_COMMENT) mdb.remove()
|
||||
// Inject new VORBIS_COMMENT block.
|
||||
if (mdb.removed || mdb.isLast) {
|
||||
let mdbVorbis = flac.data.MetaDataBlockVorbisComment.create(mdb.isLast, vendor, comments)
|
||||
if ((mdb.removed || mdb.isLast) && !isInjected) {
|
||||
isInjected = true
|
||||
let mdbVorbis = FlacComment.create(mdb.isLast, vendor, comments)
|
||||
this.push(mdbVorbis.publish())
|
||||
}
|
||||
})
|
||||
|
||||
reader.pipe(processor).pipe(writer).on('finish', () => {
|
||||
reader.pipe(flacProcessor).pipe(writer).on('finish', () => {
|
||||
fs.unlink(filenPath, err => {
|
||||
if (err) return console.log(err.message)
|
||||
fs.rename(tempPath, filenPath, err => {
|
||||
|
|
|
@ -91,7 +91,7 @@ const getUrl = (downloadInfo, isRefresh) => {
|
|||
* @param {*} isEmbedPic // 是否嵌入图片
|
||||
*/
|
||||
const saveMeta = (downloadInfo, filePath, isEmbedPic) => {
|
||||
if (downloadInfo.type === 'ape' || downloadInfo.type === 'flac') return
|
||||
if (downloadInfo.type === 'ape') return
|
||||
const promise = isEmbedPic
|
||||
? downloadInfo.musicInfo.img
|
||||
? Promise.resolve(downloadInfo.musicInfo.img)
|
||||
|
@ -104,6 +104,12 @@ const saveMeta = (downloadInfo, filePath, isEmbedPic) => {
|
|||
album: downloadInfo.musicInfo.albumName,
|
||||
APIC: url,
|
||||
})
|
||||
}).catch(() => {
|
||||
setMeta(filePath, {
|
||||
title: downloadInfo.musicInfo.name,
|
||||
artist: downloadInfo.musicInfo.singer,
|
||||
album: downloadInfo.musicInfo.albumName,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import { getProxyInfo } from './index'
|
|||
|
||||
const request = (url, options, callback) => {
|
||||
let data
|
||||
if (options.method == 'get') options.headers['Content-Length'] = 0
|
||||
if (options.body) {
|
||||
data = options.body
|
||||
} else if (options.form) {
|
||||
|
|
Loading…
Reference in New Issue