audiobookshelf/server/managers/AudioMetadataManager.js

120 lines
4.1 KiB
JavaScript

const Path = require('path')
const SocketAuthority = require('../SocketAuthority')
const Logger = require('../Logger')
const fs = require('../libs/fsExtra')
const { secondsToTimestamp } = require('../utils/index')
const toneHelpers = require('../utils/toneHelpers')
const filePerms = require('../utils/filePerms')
class AudioMetadataMangaer {
constructor(db, taskManager) {
this.db = db
this.taskManager = taskManager
}
getToneMetadataObjectForApi(libraryItem) {
return toneHelpers.getToneMetadataObject(libraryItem)
}
async updateMetadataForItem(user, libraryItem, options = {}) {
const forceEmbedChapters = !!options.forceEmbedChapters
const backupFiles = !!options.backup
const audioFiles = libraryItem.media.includedAudioFiles
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 }))
}
SocketAuthority.emitter('audio_metadata_started', itemAudioMetadataPayload)
// Ensure folder for backup files
const itemCacheDir = Path.join(global.MetadataPath, `cache/items/${libraryItem.id}`)
let cacheDirCreated = false
if (!await fs.pathExists(itemCacheDir)) {
await fs.mkdir(itemCacheDir)
await filePerms.setDefault(itemCacheDir, true)
cacheDirCreated = true
}
// Write chapters file
const toneJsonPath = Path.join(itemCacheDir, 'metadata.json')
try {
const chapters = (audioFiles.length == 1 || forceEmbedChapters) ? libraryItem.media.chapters : null
await toneHelpers.writeToneMetadataJsonFile(libraryItem, chapters, toneJsonPath, audioFiles.length)
} catch (error) {
Logger.error(`[AudioMetadataManager] Write metadata.json failed`, error)
itemAudioMetadataPayload.failed = true
itemAudioMetadataPayload.error = 'Failed to write metadata.json'
SocketAuthority.emitter('audio_metadata_finished', itemAudioMetadataPayload)
return
}
const results = []
for (const af of audioFiles) {
const result = await this.updateAudioFileMetadataWithTone(libraryItem.id, af, toneJsonPath, itemCacheDir, backupFiles)
results.push(result)
}
// Remove temp cache file/folder if not backing up
if (!backupFiles) {
// If cache dir was created from this then remove it
if (cacheDirCreated) {
await fs.remove(itemCacheDir)
} else {
await fs.remove(toneJsonPath)
}
}
const elapsed = Date.now() - itemAudioMetadataPayload.startedAt
Logger.debug(`[AudioMetadataManager] Elapsed ${secondsToTimestamp(elapsed / 1000, true)}`)
itemAudioMetadataPayload.results = results
itemAudioMetadataPayload.elapsed = elapsed
itemAudioMetadataPayload.finishedAt = Date.now()
SocketAuthority.emitter('audio_metadata_finished', itemAudioMetadataPayload)
}
async updateAudioFileMetadataWithTone(libraryItemId, audioFile, toneJsonPath, itemCacheDir, backupFiles) {
const resultPayload = {
libraryItemId,
index: audioFile.index,
ino: audioFile.ino,
filename: audioFile.metadata.filename
}
SocketAuthority.emitter('audiofile_metadata_started', resultPayload)
// Backup audio file
if (backupFiles) {
try {
const backupFilePath = Path.join(itemCacheDir, audioFile.metadata.filename)
await fs.copy(audioFile.metadata.path, backupFilePath)
Logger.debug(`[AudioMetadataManager] Backed up audio file at "${backupFilePath}"`)
} catch (err) {
Logger.error(`[AudioMetadataManager] Failed to backup audio file "${audioFile.metadata.path}"`, err)
}
}
const _toneMetadataObject = {
'ToneJsonFile': toneJsonPath,
'TrackNumber': audioFile.index,
}
resultPayload.success = await toneHelpers.tagAudioFile(audioFile.metadata.path, _toneMetadataObject)
if (resultPayload.success) {
Logger.info(`[AudioMetadataManager] Successfully tagged audio file "${audioFile.metadata.path}"`)
}
SocketAuthority.emitter('audiofile_metadata_finished', resultPayload)
return resultPayload
}
}
module.exports = AudioMetadataMangaer