mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-07-22 11:54:32 +02:00
Add:Experimental embed metadata in audio files #141
This commit is contained in:
parent
5f0f8b92d1
commit
84c12a6e7e
9 changed files with 252 additions and 19 deletions
137
server/managers/AudioMetadataManager.js
Normal file
137
server/managers/AudioMetadataManager.js
Normal file
|
@ -0,0 +1,137 @@
|
|||
const Path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const workerThreads = require('worker_threads')
|
||||
const Logger = require('../Logger')
|
||||
const filePerms = require('../utils/filePerms')
|
||||
const { secondsToTimestamp } = require('../utils/index')
|
||||
const { writeMetadataFile } = require('../utils/ffmpegHelpers')
|
||||
|
||||
class AudioMetadataMangaer {
|
||||
constructor(db, emitter, clientEmitter) {
|
||||
this.db = db
|
||||
this.emitter = emitter
|
||||
this.clientEmitter = clientEmitter
|
||||
}
|
||||
|
||||
async updateAudioFileMetadataForItem(user, libraryItem) {
|
||||
var audioFiles = libraryItem.media.audioFiles
|
||||
|
||||
const itemAudioMetadataPayload = {
|
||||
userId: user.id,
|
||||
libraryItemId: libraryItem.id,
|
||||
startedAt: Date.now(),
|
||||
audioFiles: audioFiles.map(af => ({ index: af.index, ino: af.ino, filename: af.metadata.filename }))
|
||||
}
|
||||
|
||||
this.emitter('audio_metadata_started', itemAudioMetadataPayload)
|
||||
|
||||
var downloadsPath = Path.join(global.MetadataPath, 'downloads')
|
||||
var outputDir = Path.join(downloadsPath, libraryItem.id)
|
||||
await fs.ensureDir(outputDir)
|
||||
|
||||
var metadataFilePath = Path.join(outputDir, 'metadata.txt')
|
||||
await writeMetadataFile(libraryItem, metadataFilePath)
|
||||
|
||||
const proms = audioFiles.map(af => {
|
||||
return this.updateAudioFileMetadata(libraryItem.id, af, outputDir, metadataFilePath)
|
||||
})
|
||||
|
||||
const results = await Promise.all(proms)
|
||||
|
||||
Logger.debug(`[AudioMetadataManager] Finished`, results)
|
||||
|
||||
await fs.remove(outputDir)
|
||||
|
||||
const elapsed = Date.now() - itemAudioMetadataPayload.startedAt
|
||||
Logger.debug(`[AudioMetadataManager] Elapsed ${secondsToTimestamp(elapsed)}`)
|
||||
itemAudioMetadataPayload.results = results
|
||||
itemAudioMetadataPayload.elapsed = elapsed
|
||||
itemAudioMetadataPayload.finishedAt = Date.now()
|
||||
this.emitter('audio_metadata_finished', itemAudioMetadataPayload)
|
||||
}
|
||||
|
||||
updateAudioFileMetadata(libraryItemId, audioFile, outputDir, metadataFilePath) {
|
||||
return new Promise((resolve) => {
|
||||
const resultPayload = {
|
||||
libraryItemId,
|
||||
index: audioFile.index,
|
||||
ino: audioFile.ino,
|
||||
filename: audioFile.metadata.filename
|
||||
}
|
||||
this.emitter('audiofile_metadata_started', resultPayload)
|
||||
|
||||
Logger.debug(`[AudioFileMetadataManager] Starting audio file metadata encode for "${audioFile.metadata.filename}"`)
|
||||
|
||||
var outputPath = Path.join(outputDir, audioFile.metadata.filename)
|
||||
var inputPath = audioFile.metadata.path
|
||||
const isM4b = audioFile.metadata.format === 'm4b'
|
||||
const ffmpegInputs = [
|
||||
{
|
||||
input: inputPath,
|
||||
options: isM4b ? ['-f mp4'] : []
|
||||
},
|
||||
{
|
||||
input: metadataFilePath
|
||||
}
|
||||
]
|
||||
|
||||
/*
|
||||
Mp4 doesnt support writing custom tags by default. Supported tags are itunes tags: https://git.videolan.org/?p=ffmpeg.git;a=blob;f=libavformat/movenc.c;h=b6821d447c92183101086cb67099b2f4804293de;hb=HEAD#l2905
|
||||
|
||||
Workaround -movflags use_metadata_tags found here: https://superuser.com/a/1208277
|
||||
|
||||
Ffmpeg premapped id3 tags: https://wiki.multimedia.cx/index.php/FFmpeg_Metadata
|
||||
*/
|
||||
|
||||
const ffmpegOptions = ['-c copy', '-map_metadata 1', `-metadata track=${audioFile.index}`, '-write_id3v2 1', '-movflags use_metadata_tags']
|
||||
var workerData = {
|
||||
inputs: ffmpegInputs,
|
||||
options: ffmpegOptions,
|
||||
outputOptions: isM4b ? ['-f mp4'] : [],
|
||||
output: outputPath,
|
||||
}
|
||||
var workerPath = Path.join(global.appRoot, 'server/utils/downloadWorker.js')
|
||||
var worker = new workerThreads.Worker(workerPath, { workerData })
|
||||
|
||||
worker.on('message', async (message) => {
|
||||
if (message != null && typeof message === 'object') {
|
||||
if (message.type === 'RESULT') {
|
||||
Logger.debug(message)
|
||||
|
||||
if (message.success) {
|
||||
Logger.debug(`[AudioFileMetadataManager] Metadata encode SUCCESS for "${audioFile.metadata.filename}"`)
|
||||
|
||||
await filePerms.setDefault(outputPath, true)
|
||||
|
||||
fs.move(outputPath, inputPath, { overwrite: true }).then(() => {
|
||||
Logger.debug(`[AudioFileMetadataManager] Audio file replaced successfully "${inputPath}"`)
|
||||
|
||||
resultPayload.success = true
|
||||
this.emitter('audiofile_metadata_finished', resultPayload)
|
||||
resolve(resultPayload)
|
||||
}).catch((error) => {
|
||||
Logger.error(`[AudioFileMetadataManager] Audio file failed to move "${inputPath}"`, error)
|
||||
resultPayload.success = false
|
||||
this.emitter('audiofile_metadata_finished', resultPayload)
|
||||
resolve(resultPayload)
|
||||
})
|
||||
} else {
|
||||
Logger.debug(`[AudioFileMetadataManager] Metadata encode FAILED for "${audioFile.metadata.filename}"`)
|
||||
|
||||
resultPayload.success = false
|
||||
this.emitter('audiofile_metadata_finished', resultPayload)
|
||||
resolve(resultPayload)
|
||||
}
|
||||
} else if (message.type === 'FFMPEG') {
|
||||
if (Logger[message.level]) {
|
||||
Logger[message.level](message.log)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Logger.error('Invalid worker message', message)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
module.exports = AudioMetadataMangaer
|
Loading…
Add table
Add a link
Reference in a new issue