新增FLAC格式音乐标签信息写入与封面嵌入
parent
343c53b41c
commit
a4c5670409
|
@ -9846,11 +9846,12 @@
|
|||
"dev": true
|
||||
},
|
||||
"image-size": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npm.taobao.org/image-size/download/image-size-0.5.5.tgz",
|
||||
"integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npm.taobao.org/image-size/download/image-size-0.8.3.tgz",
|
||||
"integrity": "sha1-8LVohX4DTym6/9NwE1h/LAyti0Y=",
|
||||
"requires": {
|
||||
"queue": "6.0.1"
|
||||
}
|
||||
},
|
||||
"import-cwd": {
|
||||
"version": "2.1.0",
|
||||
|
@ -9940,8 +9941,7 @@
|
|||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz?cache=0&sync_timestamp=1560975547815&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.4.tgz",
|
||||
"integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w=",
|
||||
"dev": true
|
||||
"integrity": "sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
@ -10753,6 +10753,13 @@
|
|||
"tslib": "^1.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"image-size": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npm.taobao.org/image-size/download/image-size-0.5.5.tgz",
|
||||
"integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npm.taobao.org/mime/download/mime-1.6.0.tgz",
|
||||
|
@ -13415,6 +13422,14 @@
|
|||
"integrity": "sha1-YOWl/WSn+L+k0qsu1v30yFutFU4=",
|
||||
"dev": true
|
||||
},
|
||||
"queue": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npm.taobao.org/queue/download/queue-6.0.1.tgz",
|
||||
"integrity": "sha1-q9WlsDdpEvBwolcp4Lan1WVoN5E=",
|
||||
"requires": {
|
||||
"inherits": "~2.0.3"
|
||||
}
|
||||
},
|
||||
"randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npm.taobao.org/randombytes/download/randombytes-2.1.0.tgz",
|
||||
|
|
|
@ -209,6 +209,7 @@
|
|||
"electron-log": "^4.1.0",
|
||||
"electron-store": "^5.1.1",
|
||||
"electron-updater": "^4.2.5",
|
||||
"image-size": "^0.8.3",
|
||||
"js-htmlencode": "^0.3.0",
|
||||
"lrc-file-parser": "^1.0.1",
|
||||
"needle": "^2.3.3",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
### 新增
|
||||
|
||||
- 新增FLAC格式音乐标签信息写入
|
||||
- 新增FLAC格式音乐标签信息写入与封面嵌入
|
||||
|
||||
### 优化
|
||||
|
||||
|
|
|
@ -1,10 +1,280 @@
|
|||
// https://github.com/claus/flac-metadata
|
||||
|
||||
module.exports.Processor = require('./lib/Processor')
|
||||
const Transform = require('stream').Transform
|
||||
|
||||
module.exports.data = {
|
||||
MetaDataBlock: require('./lib/data/MetaDataBlock'),
|
||||
MetaDataBlockStreamInfo: require('./lib/data/MetaDataBlockStreamInfo'),
|
||||
MetaDataBlockVorbisComment: require('./lib/data/MetaDataBlockVorbisComment'),
|
||||
MetaDataBlockPicture: require('./lib/data/MetaDataBlockPicture'),
|
||||
const MetaDataBlock = require('./lib/MetaDataBlock')
|
||||
const MetaDataBlockStreamInfo = require('./lib/MetaDataBlockStreamInfo')
|
||||
const MetaDataBlockVorbisComment = require('./lib/MetaDataBlockVorbisComment')
|
||||
const MetaDataBlockPicture = require('./lib/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
|
||||
|
||||
this.waitWriteVorbis = null
|
||||
this.waitWritePicture = null
|
||||
this.tasks = 0
|
||||
|
||||
if (!(this instanceof Processor)) return new Processor(options)
|
||||
if (options && !!options.parseMetaDataBlocks) { this.parseMetaDataBlocks = true }
|
||||
}
|
||||
|
||||
writeMeta({ vorbis, picture }) {
|
||||
if (vorbis != null) {
|
||||
this.waitWriteVorbis = vorbis
|
||||
this.tasks++
|
||||
}
|
||||
if (picture != null) {
|
||||
this.waitWritePicture = picture
|
||||
this.tasks++
|
||||
}
|
||||
}
|
||||
|
||||
// clearMeta() {
|
||||
// this.mdbLastWritten = true
|
||||
// }
|
||||
|
||||
readMeta(callback) {
|
||||
this.parseMetaDataBlocks = true
|
||||
this.readCallBack = callback
|
||||
}
|
||||
|
||||
_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.readCallBack && this.readCallBack(this.mdb)
|
||||
if (this.mdbLast) {
|
||||
this._writeVorbisComment()
|
||||
this._writePicture()
|
||||
this.state = STATE_PASS_THROUGH
|
||||
} else {
|
||||
this.state = 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 this.MDB_TYPE_STREAMINFO:
|
||||
this.mdb = new MetaDataBlockStreamInfo(this.mdbLast)
|
||||
break
|
||||
case this.MDB_TYPE_VORBIS_COMMENT:
|
||||
if (this.waitWriteVorbis) {
|
||||
this._writeVorbisComment(slice, header)
|
||||
this.mdbPush = false
|
||||
return this.mdbPush
|
||||
} else {
|
||||
this.mdb = new MetaDataBlockVorbisComment(this.mdbLast)
|
||||
this.readCallback && this.readCallback(this.mdb)
|
||||
}
|
||||
break
|
||||
case this.MDB_TYPE_PICTURE:
|
||||
if (this.waitWritePicture) {
|
||||
this._writePicture(slice, header)
|
||||
this.mdbPush = false
|
||||
return this.mdbPush
|
||||
} else {
|
||||
this.mdb = new MetaDataBlockPicture(this.mdbLast)
|
||||
this.readCallback && this.readCallback(this.mdb)
|
||||
}
|
||||
break
|
||||
case this.MDB_TYPE_PADDING:
|
||||
case this.MDB_TYPE_APPLICATION:
|
||||
case this.MDB_TYPE_SEEKTABLE:
|
||||
case this.MDB_TYPE_CUESHEET:
|
||||
case this.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 {
|
||||
if (this.mdbLast && this.tasks > 0) {
|
||||
header &= 0x7fffffff
|
||||
slice.writeUInt32BE(header >>> 0, 0)
|
||||
}
|
||||
// The consumer may change the MDB's isLast flag in the preprocess handler.
|
||||
// Here that flag is updated in the MDB header.
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
_writeVorbisComment() {
|
||||
if (this.waitWriteVorbis == null) return
|
||||
let isLast = this.mdbLast && this.tasks === 1
|
||||
this.tasks--
|
||||
this.push(MetaDataBlockVorbisComment.create(isLast, this.waitWriteVorbis.vendor, this.waitWriteVorbis.comments).publish())
|
||||
this.waitWriteVorbis = null
|
||||
}
|
||||
|
||||
_writePicture() {
|
||||
if (this.waitWritePicture == null) return
|
||||
let isLast = this.mdbLast && this.tasks === 1
|
||||
this.tasks--
|
||||
this.mdb =
|
||||
this.push(
|
||||
MetaDataBlockPicture.create(
|
||||
isLast, this.waitWritePicture.pictureType,
|
||||
this.waitWritePicture.mimeType, this.waitWritePicture.description,
|
||||
this.waitWritePicture.width, this.waitWritePicture.height,
|
||||
this.waitWritePicture.bitsPerPixel, this.waitWritePicture.colors,
|
||||
this.waitWritePicture.pictureData,
|
||||
).publish(),
|
||||
)
|
||||
this.waitWritePicture = null
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Processor
|
||||
|
|
|
@ -1,214 +0,0 @@
|
|||
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
|
|
@ -1,38 +1,94 @@
|
|||
const fs = require('fs')
|
||||
const { Processor: FlacProcessor, data: { MetaDataBlockVorbisComment: FlacComment } } = require('./flac-metadata')
|
||||
const path = require('path')
|
||||
const getImgSize = require('image-size')
|
||||
const request = require('request')
|
||||
|
||||
const FlacProcessor = require('./flac-metadata')
|
||||
|
||||
const extReg = /^(\.(?:jpe?g|png)).*$/
|
||||
const vendor = 'reference libFLAC 1.2.1 20070917'
|
||||
|
||||
module.exports = (filenPath, meta) => {
|
||||
const reader = fs.createReadStream(filenPath)
|
||||
const tempPath = filenPath + '.lxmtemp'
|
||||
|
||||
const writeMeta = (filePath, meta, picPath) => {
|
||||
const comments = Object.keys(meta).map(key => `${key.toUpperCase()}=${meta[key]}`)
|
||||
const data = {
|
||||
vorbis: {
|
||||
vendor,
|
||||
comments,
|
||||
},
|
||||
}
|
||||
if (picPath) {
|
||||
const apicData = Buffer.from(fs.readFileSync(picPath, 'binary'), 'binary')
|
||||
let imgSize = getImgSize(apicData)
|
||||
let mime_type
|
||||
let bitsPerPixel
|
||||
if (apicData[0] == 0xff && apicData[1] == 0xd8 && apicData[2] == 0xff) {
|
||||
mime_type = 'image/jpeg'
|
||||
bitsPerPixel = 24
|
||||
} else {
|
||||
mime_type = 'image/png'
|
||||
bitsPerPixel = 32
|
||||
}
|
||||
data.picture = {
|
||||
pictureType: 3,
|
||||
mimeType: mime_type,
|
||||
description: '',
|
||||
width: imgSize.width,
|
||||
height: imgSize.height,
|
||||
bitsPerPixel,
|
||||
colors: 0,
|
||||
pictureData: apicData,
|
||||
}
|
||||
}
|
||||
|
||||
const reader = fs.createReadStream(filePath)
|
||||
const tempPath = filePath + '.lxmtemp'
|
||||
const writer = fs.createWriteStream(tempPath)
|
||||
const flacProcessor = new FlacProcessor()
|
||||
if (meta.APIC) delete meta.APIC
|
||||
|
||||
const comments = []
|
||||
for (const key in meta) {
|
||||
comments.push(`${key.toUpperCase()}=${meta[key]}`)
|
||||
}
|
||||
|
||||
let isInjected = false
|
||||
flacProcessor.on('preprocess', function(mdb) {
|
||||
// Remove existing VORBIS_COMMENT block, if any.
|
||||
if (mdb.type === flacProcessor.MDB_TYPE_VORBIS_COMMENT) mdb.remove()
|
||||
// Inject new VORBIS_COMMENT block.
|
||||
if ((mdb.removed || mdb.isLast) && !isInjected) {
|
||||
isInjected = true
|
||||
let mdbVorbis = FlacComment.create(mdb.isLast, vendor, comments)
|
||||
this.push(mdbVorbis.publish())
|
||||
}
|
||||
})
|
||||
flacProcessor.writeMeta(data)
|
||||
|
||||
reader.pipe(flacProcessor).pipe(writer).on('finish', () => {
|
||||
fs.unlink(filenPath, err => {
|
||||
fs.unlink(filePath, err => {
|
||||
if (err) return console.log(err.message)
|
||||
fs.rename(tempPath, filenPath, err => {
|
||||
fs.rename(tempPath, filePath, err => {
|
||||
if (err) console.log(err.message)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = (filePath, meta) => {
|
||||
if (!meta.APIC) return writeMeta(filePath, meta)
|
||||
const picUrl = meta.APIC
|
||||
delete meta.APIC
|
||||
if (!/^http/.test(picUrl)) {
|
||||
return writeMeta(filePath, meta)
|
||||
}
|
||||
let picPath = filePath.replace(/\.flac$/, '') + path.extname(picUrl).replace(extReg, '$1')
|
||||
|
||||
request(picUrl)
|
||||
.on('response', respones => {
|
||||
if (respones.statusCode !== 200 && respones.statusCode != 206) return writeMeta(filePath, meta)
|
||||
respones
|
||||
.pipe(fs.createWriteStream(picPath))
|
||||
.on('finish', () => {
|
||||
if (respones.complete) {
|
||||
writeMeta(filePath, meta, picPath)
|
||||
} else {
|
||||
writeMeta(filePath, meta)
|
||||
}
|
||||
fs.unlink(picPath, err => {
|
||||
if (err) console.log(err.message)
|
||||
})
|
||||
})
|
||||
.on('error', err => {
|
||||
if (err) console.log(err.message)
|
||||
writeMeta(filePath, meta)
|
||||
})
|
||||
})
|
||||
.on('error', err => {
|
||||
if (err) console.log(err.message)
|
||||
writeMeta(filePath, meta)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ module.exports = (filePath, meta) => {
|
|||
NodeID3.write(meta, filePath)
|
||||
} else {
|
||||
delete meta.APIC
|
||||
NodeID3.write(meta, filePath)
|
||||
}
|
||||
fs.unlink(picPath, err => {
|
||||
if (err) console.log(err.message)
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
"download_name_title": "下载歌曲时的命名方式",
|
||||
"download_name": "文件命名方式",
|
||||
"download_embed_pic_title": "是否将封面嵌入音频文件中",
|
||||
"download_embed_pic": "封面嵌入(只支持MP3格式)",
|
||||
"download_embed_pic": "封面嵌入",
|
||||
"download_lyric_title": "是否同时下载歌词文件",
|
||||
"download_lyric": "歌词下载",
|
||||
"download_name1": "歌名 - 歌手",
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
"download_name_title": "下載歌曲時的命名方式",
|
||||
"download_name": "文件命名方式",
|
||||
"download_embed_pic_title": "是否將封面嵌入音頻文件中",
|
||||
"download_embed_pic": "封面嵌入(只支持MP3格式)",
|
||||
"download_embed_pic": "封面嵌入",
|
||||
"download_lyric_title": "是否同時下載歌詞文件",
|
||||
"download_lyric": "歌詞下載",
|
||||
"download_name1": "歌名 - 歌手",
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
"download_name_title": "Naming when downloading songs",
|
||||
"download_name": "File naming",
|
||||
"download_embed_pic_title": "Whether to embed the cover in the audio file",
|
||||
"download_embed_pic": "Cover embedding (only supports MP3 format)",
|
||||
"download_embed_pic": "Cover embedding",
|
||||
"download_lyric_title": "Whether to download lyrics files at the same time",
|
||||
"download_lyric": "Lyrics download",
|
||||
"download_name1": "name - singer",
|
||||
|
|
Loading…
Reference in New Issue