mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Replace tone with ffmpeg in AbMergeManager
This commit is contained in:
		
							parent
							
								
									4732ca8119
								
							
						
					
					
						commit
						ebaec23648
					
				@ -1,4 +1,3 @@
 | 
				
			|||||||
 | 
					 | 
				
			||||||
const Path = require('path')
 | 
					const Path = require('path')
 | 
				
			||||||
const fs = require('../libs/fsExtra')
 | 
					const fs = require('../libs/fsExtra')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -7,7 +6,7 @@ const Logger = require('../Logger')
 | 
				
			|||||||
const TaskManager = require('./TaskManager')
 | 
					const TaskManager = require('./TaskManager')
 | 
				
			||||||
const Task = require('../objects/Task')
 | 
					const Task = require('../objects/Task')
 | 
				
			||||||
const { writeConcatFile } = require('../utils/ffmpegHelpers')
 | 
					const { writeConcatFile } = require('../utils/ffmpegHelpers')
 | 
				
			||||||
const toneHelpers = require('../utils/toneHelpers')
 | 
					const ffmpegHelpers = require('../utils/ffmpegHelpers')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AbMergeManager {
 | 
					class AbMergeManager {
 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
@ -17,7 +16,7 @@ class AbMergeManager {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getPendingTaskByLibraryItemId(libraryItemId) {
 | 
					  getPendingTaskByLibraryItemId(libraryItemId) {
 | 
				
			||||||
    return this.pendingTasks.find(t => t.task.data.libraryItemId === libraryItemId)
 | 
					    return this.pendingTasks.find((t) => t.task.data.libraryItemId === libraryItemId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  cancelEncode(task) {
 | 
					  cancelEncode(task) {
 | 
				
			||||||
@ -31,23 +30,27 @@ class AbMergeManager {
 | 
				
			|||||||
    const targetFilename = audiobookDirname + '.m4b'
 | 
					    const targetFilename = audiobookDirname + '.m4b'
 | 
				
			||||||
    const itemCachePath = Path.join(this.itemsCacheDir, libraryItem.id)
 | 
					    const itemCachePath = Path.join(this.itemsCacheDir, libraryItem.id)
 | 
				
			||||||
    const tempFilepath = Path.join(itemCachePath, targetFilename)
 | 
					    const tempFilepath = Path.join(itemCachePath, targetFilename)
 | 
				
			||||||
 | 
					    const ffmetadataPath = Path.join(itemCachePath, 'ffmetadata.txt')
 | 
				
			||||||
    const taskData = {
 | 
					    const taskData = {
 | 
				
			||||||
      libraryItemId: libraryItem.id,
 | 
					      libraryItemId: libraryItem.id,
 | 
				
			||||||
      libraryItemPath: libraryItem.path,
 | 
					      libraryItemPath: libraryItem.path,
 | 
				
			||||||
      userId: user.id,
 | 
					      userId: user.id,
 | 
				
			||||||
      originalTrackPaths: libraryItem.media.tracks.map(t => t.metadata.path),
 | 
					      originalTrackPaths: libraryItem.media.tracks.map((t) => t.metadata.path),
 | 
				
			||||||
      tempFilepath,
 | 
					      tempFilepath,
 | 
				
			||||||
      targetFilename,
 | 
					      targetFilename,
 | 
				
			||||||
      targetFilepath: Path.join(libraryItem.path, targetFilename),
 | 
					      targetFilepath: Path.join(libraryItem.path, targetFilename),
 | 
				
			||||||
      itemCachePath,
 | 
					      itemCachePath,
 | 
				
			||||||
      toneJsonObject: null
 | 
					      ffmetadataObject: ffmpegHelpers.getFFMetadataObject(libraryItem, 1),
 | 
				
			||||||
 | 
					      chapters: libraryItem.media.chapters?.map((c) => ({ ...c })),
 | 
				
			||||||
 | 
					      coverPath: libraryItem.media.coverPath,
 | 
				
			||||||
 | 
					      ffmetadataPath
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const taskDescription = `Encoding audiobook "${libraryItem.media.metadata.title}" into a single m4b file.`
 | 
					    const taskDescription = `Encoding audiobook "${libraryItem.media.metadata.title}" into a single m4b file.`
 | 
				
			||||||
    task.setData('encode-m4b', 'Encoding M4b', taskDescription, false, taskData)
 | 
					    task.setData('encode-m4b', 'Encoding M4b', taskDescription, false, taskData)
 | 
				
			||||||
    TaskManager.addTask(task)
 | 
					    TaskManager.addTask(task)
 | 
				
			||||||
    Logger.info(`Start m4b encode for ${libraryItem.id} - TaskId: ${task.id}`)
 | 
					    Logger.info(`Start m4b encode for ${libraryItem.id} - TaskId: ${task.id}`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!await fs.pathExists(taskData.itemCachePath)) {
 | 
					    if (!(await fs.pathExists(taskData.itemCachePath))) {
 | 
				
			||||||
      await fs.mkdir(taskData.itemCachePath)
 | 
					      await fs.mkdir(taskData.itemCachePath)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -55,6 +58,15 @@ class AbMergeManager {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async runAudiobookMerge(libraryItem, task, encodingOptions) {
 | 
					  async runAudiobookMerge(libraryItem, task, encodingOptions) {
 | 
				
			||||||
 | 
					    // Create ffmetadata file
 | 
				
			||||||
 | 
					    const success = await ffmpegHelpers.writeFFMetadataFile(task.data.metadataObject, task.data.chapters, task.data.ffmetadataPath)
 | 
				
			||||||
 | 
					    if (!success) {
 | 
				
			||||||
 | 
					      Logger.error(`[AudioMetadataManager] Failed to write ffmetadata file for audiobook "${task.data.libraryItemId}"`)
 | 
				
			||||||
 | 
					      task.setFailed('Failed to write metadata file.')
 | 
				
			||||||
 | 
					      this.removeTask(task, true)
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const audioBitrate = encodingOptions.bitrate || '128k'
 | 
					    const audioBitrate = encodingOptions.bitrate || '128k'
 | 
				
			||||||
    const audioCodec = encodingOptions.codec || 'aac'
 | 
					    const audioCodec = encodingOptions.codec || 'aac'
 | 
				
			||||||
    const audioChannels = encodingOptions.channels || 2
 | 
					    const audioChannels = encodingOptions.channels || 2
 | 
				
			||||||
@ -90,12 +102,7 @@ class AbMergeManager {
 | 
				
			|||||||
    const ffmpegOutputOptions = ['-f mp4']
 | 
					    const ffmpegOutputOptions = ['-f mp4']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (audioRequiresEncode) {
 | 
					    if (audioRequiresEncode) {
 | 
				
			||||||
      ffmpegOptions = ffmpegOptions.concat([
 | 
					      ffmpegOptions = ffmpegOptions.concat(['-map 0:a', `-acodec ${audioCodec}`, `-ac ${audioChannels}`, `-b:a ${audioBitrate}`])
 | 
				
			||||||
        '-map 0:a',
 | 
					 | 
				
			||||||
        `-acodec ${audioCodec}`,
 | 
					 | 
				
			||||||
        `-ac ${audioChannels}`,
 | 
					 | 
				
			||||||
        `-b:a ${audioBitrate}`
 | 
					 | 
				
			||||||
      ])
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      ffmpegOptions.push('-max_muxing_queue_size 1000')
 | 
					      ffmpegOptions.push('-max_muxing_queue_size 1000')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -106,24 +113,6 @@ class AbMergeManager {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let toneJsonPath = null
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      toneJsonPath = Path.join(task.data.itemCachePath, 'metadata.json')
 | 
					 | 
				
			||||||
      await toneHelpers.writeToneMetadataJsonFile(libraryItem, libraryItem.media.chapters, toneJsonPath, 1, 'audio/mp4')
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      Logger.error(`[AbMergeManager] Write metadata.json failed`, error)
 | 
					 | 
				
			||||||
      toneJsonPath = null
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    task.data.toneJsonObject = {
 | 
					 | 
				
			||||||
      'ToneJsonFile': toneJsonPath,
 | 
					 | 
				
			||||||
      'TrackNumber': 1,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (libraryItem.media.coverPath) {
 | 
					 | 
				
			||||||
      task.data.toneJsonObject['CoverFile'] = libraryItem.media.coverPath
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const workerData = {
 | 
					    const workerData = {
 | 
				
			||||||
      inputs: ffmpegInputs,
 | 
					      inputs: ffmpegInputs,
 | 
				
			||||||
      options: ffmpegOptions,
 | 
					      options: ffmpegOptions,
 | 
				
			||||||
@ -162,7 +151,7 @@ class AbMergeManager {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  async sendResult(task, result) {
 | 
					  async sendResult(task, result) {
 | 
				
			||||||
    // Remove pending task
 | 
					    // Remove pending task
 | 
				
			||||||
    this.pendingTasks = this.pendingTasks.filter(d => d.id !== task.id)
 | 
					    this.pendingTasks = this.pendingTasks.filter((d) => d.id !== task.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (result.isKilled) {
 | 
					    if (result.isKilled) {
 | 
				
			||||||
      task.setFailed('Ffmpeg task killed')
 | 
					      task.setFailed('Ffmpeg task killed')
 | 
				
			||||||
@ -177,7 +166,7 @@ class AbMergeManager {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Write metadata to merged file
 | 
					    // Write metadata to merged file
 | 
				
			||||||
    const success = await toneHelpers.tagAudioFile(task.data.tempFilepath, task.data.toneJsonObject)
 | 
					    const success = await ffmpegHelpers.addCoverAndMetadataToFile(task.data.tempFilepath, task.data.coverPath, task.data.ffmetadataPath, 1, 'audio/mp4')
 | 
				
			||||||
    if (!success) {
 | 
					    if (!success) {
 | 
				
			||||||
      Logger.error(`[AbMergeManager] Failed to write metadata to file "${task.data.tempFilepath}"`)
 | 
					      Logger.error(`[AbMergeManager] Failed to write metadata to file "${task.data.tempFilepath}"`)
 | 
				
			||||||
      task.setFailed('Failed to write metadata to m4b file')
 | 
					      task.setFailed('Failed to write metadata to m4b file')
 | 
				
			||||||
@ -199,6 +188,9 @@ class AbMergeManager {
 | 
				
			|||||||
    Logger.debug(`[AbMergeManager] Moving m4b from ${task.data.tempFilepath} to ${task.data.targetFilepath}`)
 | 
					    Logger.debug(`[AbMergeManager] Moving m4b from ${task.data.tempFilepath} to ${task.data.targetFilepath}`)
 | 
				
			||||||
    await fs.move(task.data.tempFilepath, task.data.targetFilepath)
 | 
					    await fs.move(task.data.tempFilepath, task.data.targetFilepath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Remove ffmetadata file
 | 
				
			||||||
 | 
					    await fs.remove(task.data.ffmetadataPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    task.setFinished()
 | 
					    task.setFinished()
 | 
				
			||||||
    await this.removeTask(task, false)
 | 
					    await this.removeTask(task, false)
 | 
				
			||||||
    Logger.info(`[AbMergeManager] Ab task finished ${task.id}`)
 | 
					    Logger.info(`[AbMergeManager] Ab task finished ${task.id}`)
 | 
				
			||||||
@ -207,9 +199,9 @@ class AbMergeManager {
 | 
				
			|||||||
  async removeTask(task, removeTempFilepath = false) {
 | 
					  async removeTask(task, removeTempFilepath = false) {
 | 
				
			||||||
    Logger.info('[AbMergeManager] Removing task ' + task.id)
 | 
					    Logger.info('[AbMergeManager] Removing task ' + task.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const pendingDl = this.pendingTasks.find(d => d.id === task.id)
 | 
					    const pendingDl = this.pendingTasks.find((d) => d.id === task.id)
 | 
				
			||||||
    if (pendingDl) {
 | 
					    if (pendingDl) {
 | 
				
			||||||
      this.pendingTasks = this.pendingTasks.filter(d => d.id !== task.id)
 | 
					      this.pendingTasks = this.pendingTasks.filter((d) => d.id !== task.id)
 | 
				
			||||||
      if (pendingDl.worker) {
 | 
					      if (pendingDl.worker) {
 | 
				
			||||||
        Logger.warn(`[AbMergeManager] Removing download in progress - stopping worker`)
 | 
					        Logger.warn(`[AbMergeManager] Removing download in progress - stopping worker`)
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
@ -223,14 +215,28 @@ class AbMergeManager {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (removeTempFilepath) { // On failed tasks remove the bad file if it exists
 | 
					    if (removeTempFilepath) {
 | 
				
			||||||
 | 
					      // On failed tasks remove the bad file if it exists
 | 
				
			||||||
      if (await fs.pathExists(task.data.tempFilepath)) {
 | 
					      if (await fs.pathExists(task.data.tempFilepath)) {
 | 
				
			||||||
        await fs.remove(task.data.tempFilepath).then(() => {
 | 
					        await fs
 | 
				
			||||||
 | 
					          .remove(task.data.tempFilepath)
 | 
				
			||||||
 | 
					          .then(() => {
 | 
				
			||||||
            Logger.info('[AbMergeManager] Deleted target file', task.data.tempFilepath)
 | 
					            Logger.info('[AbMergeManager] Deleted target file', task.data.tempFilepath)
 | 
				
			||||||
        }).catch((err) => {
 | 
					          })
 | 
				
			||||||
 | 
					          .catch((err) => {
 | 
				
			||||||
            Logger.error('[AbMergeManager] Failed to delete target file', err)
 | 
					            Logger.error('[AbMergeManager] Failed to delete target file', err)
 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      if (await fs.pathExists(task.data.ffmetadataPath)) {
 | 
				
			||||||
 | 
					        await fs
 | 
				
			||||||
 | 
					          .remove(task.data.ffmetadataPath)
 | 
				
			||||||
 | 
					          .then(() => {
 | 
				
			||||||
 | 
					            Logger.info('[AbMergeManager] Deleted ffmetadata file', task.data.ffmetadataPath)
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					          .catch((err) => {
 | 
				
			||||||
 | 
					            Logger.error('[AbMergeManager] Failed to delete ffmetadata file', err)
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    TaskManager.taskFinished(task)
 | 
					    TaskManager.taskFinished(task)
 | 
				
			||||||
 | 
				
			|||||||
@ -108,14 +108,12 @@ class AudioMetadataMangaer {
 | 
				
			|||||||
      cacheDirCreated = true
 | 
					      cacheDirCreated = true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Create metadata json file
 | 
					    // Create ffmetadata file
 | 
				
			||||||
    const ffmetadataPath = Path.join(task.data.itemCachePath, 'ffmetadata.txt')
 | 
					    const ffmetadataPath = Path.join(task.data.itemCachePath, 'ffmetadata.txt')
 | 
				
			||||||
    try {
 | 
					    const success = await ffmpegHelpers.writeFFMetadataFile(task.data.metadataObject, task.data.chapters, ffmetadataPath)
 | 
				
			||||||
      await fs.writeFile(ffmetadataPath, ffmpegHelpers.generateFFMetadata(task.data.metadataObject, task.data.chapters))
 | 
					    if (!success) {
 | 
				
			||||||
      Logger.debug(`[AudioMetadataManager] Wrote ${ffmetadataPath}`)
 | 
					      Logger.error(`[AudioMetadataManager] Failed to write ffmetadata file for audiobook "${task.data.libraryItemId}"`)
 | 
				
			||||||
    } catch (error) {
 | 
					      task.setFailed('Failed to write metadata file.')
 | 
				
			||||||
      Logger.error(`[AudioMetadataManager] Write ${ffmetadataPath} failed`, error)
 | 
					 | 
				
			||||||
      task.setFailed('Failed to write file ffmetadata.txt')
 | 
					 | 
				
			||||||
      this.handleTaskFinished(task)
 | 
					      this.handleTaskFinished(task)
 | 
				
			||||||
      return
 | 
					      return
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -220,6 +220,19 @@ function generateFFMetadata(metadata, chapters) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
module.exports.generateFFMetadata = generateFFMetadata
 | 
					module.exports.generateFFMetadata = generateFFMetadata
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function writeFFMetadataFile(metadata, chapters, ffmetadataPath) {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    await fs.writeFile(ffmetadataPath, generateFFMetadata(metadata, chapters))
 | 
				
			||||||
 | 
					    Logger.debug(`[ffmpegHelpers] Wrote ${ffmetadataPath}`)
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    Logger.error(`[ffmpegHelpers] Write ${ffmetadataPath} failed`, error)
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports.writeFFMetadataFile = writeFFMetadataFile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Adds an ffmetadata and optionally a cover image to an audio file using fluent-ffmpeg.
 | 
					 * Adds an ffmetadata and optionally a cover image to an audio file using fluent-ffmpeg.
 | 
				
			||||||
 * @param {string} audioFilePath - Path to the input audio file.
 | 
					 * @param {string} audioFilePath - Path to the input audio file.
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user