2022-05-02 01:33:46 +02:00
const Path = require ( 'path' )
2022-11-24 22:53:58 +01:00
const SocketAuthority = require ( '../SocketAuthority' )
2022-05-02 01:33:46 +02:00
const Logger = require ( '../Logger' )
2022-11-24 22:53:58 +01:00
const fs = require ( '../libs/fsExtra' )
2022-09-25 22:56:06 +02:00
const toneHelpers = require ( '../utils/toneHelpers' )
2023-04-02 23:13:18 +02:00
const Task = require ( '../objects/Task' )
2022-05-02 01:33:46 +02:00
class AudioMetadataMangaer {
2022-11-24 22:53:58 +01:00
constructor ( db , taskManager ) {
2022-05-02 01:33:46 +02:00
this . db = db
2022-10-02 21:16:17 +02:00
this . taskManager = taskManager
2023-04-02 23:13:18 +02:00
this . itemsCacheDir = Path . join ( global . MetadataPath , 'cache/items' )
this . MAX _CONCURRENT _TASKS = 1
this . tasksRunning = [ ]
this . tasksQueued = [ ]
}
/ * *
* Get queued task data
* @ return { Array }
* /
getQueuedTaskData ( ) {
return this . tasksQueued . map ( t => t . data )
}
getIsLibraryItemQueuedOrProcessing ( libraryItemId ) {
return this . tasksQueued . some ( t => t . data . libraryItemId === libraryItemId ) || this . tasksRunning . some ( t => t . data . libraryItemId === libraryItemId )
2022-05-02 01:33:46 +02:00
}
2022-09-25 22:56:06 +02:00
getToneMetadataObjectForApi ( libraryItem ) {
2023-04-02 23:13:18 +02:00
return toneHelpers . getToneMetadataObject ( libraryItem , libraryItem . media . chapters , libraryItem . media . tracks . length )
}
handleBatchEmbed ( user , libraryItems , options = { } ) {
libraryItems . forEach ( ( li ) => {
this . updateMetadataForItem ( user , li , options )
} )
2022-09-25 22:56:06 +02:00
}
2023-01-07 22:16:52 +01:00
async updateMetadataForItem ( user , libraryItem , options = { } ) {
const forceEmbedChapters = ! ! options . forceEmbedChapters
const backupFiles = ! ! options . backup
const audioFiles = libraryItem . media . includedAudioFiles
2022-09-25 22:56:06 +02:00
2023-04-02 23:13:18 +02:00
const task = new Task ( )
const itemCachePath = Path . join ( this . itemsCacheDir , libraryItem . id )
// Only writing chapters for single file audiobooks
const chapters = ( audioFiles . length == 1 || forceEmbedChapters ) ? libraryItem . media . chapters . map ( c => ( { ... c } ) ) : null
// Create task
const taskData = {
2022-09-25 22:56:06 +02:00
libraryItemId : libraryItem . id ,
2023-04-02 23:13:18 +02:00
libraryItemPath : libraryItem . path ,
userId : user . id ,
audioFiles : audioFiles . map ( af => (
{
index : af . index ,
ino : af . ino ,
filename : af . metadata . filename ,
path : af . metadata . path ,
cachePath : Path . join ( itemCachePath , af . metadata . filename )
}
) ) ,
coverPath : libraryItem . media . coverPath ,
metadataObject : toneHelpers . getToneMetadataObject ( libraryItem , chapters , audioFiles . length ) ,
itemCachePath ,
chapters ,
options : {
forceEmbedChapters ,
backupFiles
}
2022-09-25 22:56:06 +02:00
}
2023-04-02 23:13:18 +02:00
const taskDescription = ` Embedding metadata in audiobook " ${ libraryItem . media . metadata . title } ". `
task . setData ( 'embed-metadata' , 'Embedding Metadata' , taskDescription , taskData )
if ( this . tasksRunning . length >= this . MAX _CONCURRENT _TASKS ) {
Logger . info ( ` [AudioMetadataManager] Queueing embed metadata for audiobook " ${ libraryItem . media . metadata . title } " ` )
SocketAuthority . adminEmitter ( 'metadata_embed_queue_update' , {
libraryItemId : libraryItem . id ,
queued : true
} )
this . tasksQueued . push ( task )
} else {
this . runMetadataEmbed ( task )
}
}
2022-09-25 22:56:06 +02:00
2023-04-02 23:13:18 +02:00
async runMetadataEmbed ( task ) {
this . tasksRunning . push ( task )
this . taskManager . addTask ( task )
2022-09-25 22:56:06 +02:00
2023-04-02 23:13:18 +02:00
Logger . info ( ` [AudioMetadataManager] Starting metadata embed task ` , task . description )
// Ensure item cache dir exists
2023-01-07 22:16:52 +01:00
let cacheDirCreated = false
2023-04-02 23:13:18 +02:00
if ( ! await fs . pathExists ( task . data . itemCachePath ) ) {
await fs . mkdir ( task . data . itemCachePath )
2023-01-07 22:16:52 +01:00
cacheDirCreated = true
}
2023-04-02 23:13:18 +02:00
// Create metadata json file
const toneJsonPath = Path . join ( task . data . itemCachePath , 'metadata.json' )
2022-11-03 02:40:50 +01:00
try {
2023-04-02 23:13:18 +02:00
await fs . writeFile ( toneJsonPath , JSON . stringify ( { meta : task . data . metadataObject } , null , 2 ) )
2022-11-03 02:40:50 +01:00
} catch ( error ) {
Logger . error ( ` [AudioMetadataManager] Write metadata.json failed ` , error )
2023-04-02 23:13:18 +02:00
task . setFailed ( 'Failed to write metadata.json' )
this . handleTaskFinished ( task )
2023-01-07 22:16:52 +01:00
return
2022-09-25 22:56:06 +02:00
}
2023-04-02 23:13:18 +02:00
// Tag audio files
for ( const af of task . data . audioFiles ) {
SocketAuthority . adminEmitter ( 'audiofile_metadata_started' , {
libraryItemId : task . data . libraryItemId ,
ino : af . ino
} )
// Backup audio file
if ( task . data . options . backupFiles ) {
try {
const backupFilePath = Path . join ( task . data . itemCachePath , af . filename )
await fs . copy ( af . path , backupFilePath )
Logger . debug ( ` [AudioMetadataManager] Backed up audio file at " ${ backupFilePath } " ` )
} catch ( err ) {
Logger . error ( ` [AudioMetadataManager] Failed to backup audio file " ${ af . path } " ` , err )
}
}
const _toneMetadataObject = {
'ToneJsonFile' : toneJsonPath ,
'TrackNumber' : af . index ,
}
if ( task . data . coverPath ) {
_toneMetadataObject [ 'CoverFile' ] = task . data . coverPath
}
const success = await toneHelpers . tagAudioFile ( af . path , _toneMetadataObject )
if ( success ) {
Logger . info ( ` [AudioMetadataManager] Successfully tagged audio file " ${ af . path } " ` )
}
SocketAuthority . adminEmitter ( 'audiofile_metadata_finished' , {
libraryItemId : task . data . libraryItemId ,
ino : af . ino
} )
2022-09-25 22:56:06 +02:00
}
2023-01-07 22:16:52 +01:00
// Remove temp cache file/folder if not backing up
2023-04-02 23:13:18 +02:00
if ( ! task . data . options . backupFiles ) {
2023-01-07 22:16:52 +01:00
// If cache dir was created from this then remove it
if ( cacheDirCreated ) {
2023-04-02 23:13:18 +02:00
await fs . remove ( task . data . itemCachePath )
2023-01-07 22:16:52 +01:00
} else {
await fs . remove ( toneJsonPath )
}
}
2023-04-02 23:13:18 +02:00
task . setFinished ( )
this . handleTaskFinished ( task )
2022-09-25 22:56:06 +02:00
}
2023-04-02 23:13:18 +02:00
handleTaskFinished ( task ) {
this . taskManager . taskFinished ( task )
this . tasksRunning = this . tasksRunning . filter ( t => t . id !== task . id )
if ( this . tasksRunning . length < this . MAX _CONCURRENT _TASKS && this . tasksQueued . length ) {
Logger . info ( ` [AudioMetadataManager] Task finished and dequeueing next task. ${ this . tasksQueued } tasks queued. ` )
const nextTask = this . tasksQueued . shift ( )
SocketAuthority . emitter ( 'metadata_embed_queue_update' , {
libraryItemId : nextTask . data . libraryItemId ,
queued : false
} )
this . runMetadataEmbed ( nextTask )
} else if ( this . tasksRunning . length > 0 ) {
Logger . debug ( ` [AudioMetadataManager] Task finished but not dequeueing. Currently running ${ this . tasksRunning . length } tasks. ${ this . tasksQueued . length } tasks queued. ` )
} else {
Logger . debug ( ` [AudioMetadataManager] Task finished and no tasks remain in queue ` )
2022-09-25 22:56:06 +02:00
}
}
2022-05-02 01:33:46 +02:00
}
2022-06-04 19:17:42 +02:00
module . exports = AudioMetadataMangaer