mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Merge pull request #1110 from keaganhilliard/tone-json
Added tone json file support
This commit is contained in:
		
						commit
						9627f58541
					
				| @ -91,6 +91,10 @@ | ||||
|             A backup of your original audio files will be stored in <span class="rounded-md bg-neutral-600 text-sm text-white py-0.5 px-1 font-mono">/metadata/cache/items/{{ libraryItemId }}/</span>. Make sure to periodically purge items cache. | ||||
|           </p> | ||||
|         </div> | ||||
|         <div v-if="selectedTool === 'embed' && audioFiles.length > 1" class="flex items-start mb-2"> | ||||
|           <span class="material-icons text-base text-warning pt-1">star</span> | ||||
|           <p class="text-gray-200 ml-2">Chapters are not embedded in multi-track audiobooks.</p> | ||||
|         </div> | ||||
|         <div v-if="selectedTool === 'm4b'" class="flex items-start mb-2"> | ||||
|           <span class="material-icons text-base text-warning pt-1">star</span> | ||||
|           <p class="text-gray-200 ml-2">Encoding can take up to 30 minutes.</p> | ||||
|  | ||||
| @ -398,7 +398,8 @@ class LibraryItemController { | ||||
|     } | ||||
| 
 | ||||
|     const useTone = req.query.tone === '1' | ||||
|     this.audioMetadataManager.updateMetadataForItem(req.user, req.libraryItem, useTone) | ||||
|     const forceEmbedChapters = req.query.forceEmbedChapters === '1' | ||||
|     this.audioMetadataManager.updateMetadataForItem(req.user, req.libraryItem, useTone, forceEmbedChapters) | ||||
|     res.sendStatus(200) | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -62,7 +62,7 @@ class AbMergeManager { | ||||
|       targetFilename, | ||||
|       targetFilepath: Path.join(libraryItem.path, targetFilename), | ||||
|       itemCachePath, | ||||
|       toneMetadataObject: null | ||||
|       toneJsonObject: null | ||||
|     } | ||||
|     const taskDescription = `Encoding audiobook "${libraryItem.media.metadata.title}" into a single m4b file.` | ||||
|     task.setData('encode-m4b', 'Encoding M4b', taskDescription, taskData) | ||||
| @ -120,22 +120,19 @@ class AbMergeManager { | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     var chaptersFilePath = null | ||||
|     if (libraryItem.media.chapters.length) { | ||||
|       chaptersFilePath = Path.join(task.data.itemCachePath, 'chapters.txt') | ||||
|     var toneJsonPath = null | ||||
|     try { | ||||
|         await toneHelpers.writeToneChaptersFile(libraryItem.media.chapters, chaptersFilePath) | ||||
|       toneJsonPath = Path.join(task.data.itemCachePath, 'metadata.json') | ||||
|       await toneHelpers.writeToneMetadataJsonFile(libraryItem, libraryItem.media.chapters, toneJsonPath, 1) | ||||
|     } catch (error) { | ||||
|         Logger.error(`[AbMergeManager] Write chapters.txt failed`, error) | ||||
|         chaptersFilePath = null | ||||
|       } | ||||
|       Logger.error(`[AbMergeManager] Write metadata.json failed`, error) | ||||
|       toneJsonPath = null | ||||
|     } | ||||
| 
 | ||||
|     const toneMetadataObject = toneHelpers.getToneMetadataObject(libraryItem, chaptersFilePath) | ||||
|     toneMetadataObject.TrackNumber = 1 | ||||
|     task.data.toneMetadataObject = toneMetadataObject | ||||
| 
 | ||||
|     Logger.debug(`[AbMergeManager] Book "${libraryItem.media.metadata.title}" tone metadata object=`, toneMetadataObject) | ||||
|     task.data.toneJsonObject = { | ||||
|       'ToneJsonFile': toneJsonPath, | ||||
|       'TrackNumber': 1, | ||||
|     } | ||||
| 
 | ||||
|     var workerData = { | ||||
|       inputs: ffmpegInputs, | ||||
| @ -190,7 +187,7 @@ class AbMergeManager { | ||||
|     } | ||||
| 
 | ||||
|     // Write metadata to merged file
 | ||||
|     const success = await toneHelpers.tagAudioFile(task.data.tempFilepath, task.data.toneMetadataObject) | ||||
|     const success = await toneHelpers.tagAudioFile(task.data.tempFilepath, task.data.toneJsonObject) | ||||
|     if (!success) { | ||||
|       Logger.error(`[AbMergeManager] Failed to write metadata to file "${task.data.tempFilepath}"`) | ||||
|       task.setFailed('Failed to write metadata to m4b file') | ||||
|  | ||||
| @ -15,9 +15,9 @@ class AudioMetadataMangaer { | ||||
|     this.clientEmitter = clientEmitter | ||||
|   } | ||||
| 
 | ||||
|   updateMetadataForItem(user, libraryItem, useTone = true) { | ||||
|   updateMetadataForItem(user, libraryItem, useTone, forceEmbedChapters) { | ||||
|     if (useTone) { | ||||
|       this.updateMetadataForItemWithTone(user, libraryItem) | ||||
|       this.updateMetadataForItemWithTone(user, libraryItem, forceEmbedChapters) | ||||
|     } else { | ||||
|       this.updateMetadataForItemWithFfmpeg(user, libraryItem) | ||||
|     } | ||||
| @ -30,7 +30,7 @@ class AudioMetadataMangaer { | ||||
|     return toneHelpers.getToneMetadataObject(libraryItem) | ||||
|   } | ||||
| 
 | ||||
|   async updateMetadataForItemWithTone(user, libraryItem) { | ||||
|   async updateMetadataForItemWithTone(user, libraryItem, forceEmbedChapters) { | ||||
|     var audioFiles = libraryItem.media.includedAudioFiles | ||||
| 
 | ||||
|     const itemAudioMetadataPayload = { | ||||
| @ -43,26 +43,22 @@ class AudioMetadataMangaer { | ||||
|     this.emitter('audio_metadata_started', itemAudioMetadataPayload) | ||||
| 
 | ||||
|     // Write chapters file
 | ||||
|     var chaptersFilePath = null | ||||
|     var toneJsonPath = null | ||||
|     const itemCacheDir = Path.join(global.MetadataPath, `cache/items/${libraryItem.id}`) | ||||
|     await fs.ensureDir(itemCacheDir) | ||||
| 
 | ||||
|     if (libraryItem.media.chapters.length) { | ||||
|       chaptersFilePath = Path.join(itemCacheDir, 'chapters.txt') | ||||
|     try { | ||||
|         await toneHelpers.writeToneChaptersFile(libraryItem.media.chapters, chaptersFilePath) | ||||
|       toneJsonPath = Path.join(itemCacheDir, 'metadata.json') | ||||
|       const chapters = (audioFiles.length == 1 || forceEmbedChapters) ? libraryItem.media.chapters : null | ||||
|       await toneHelpers.writeToneMetadataJsonFile(libraryItem, chapters, toneJsonPath, audioFiles.length) | ||||
|     } catch (error) { | ||||
|         Logger.error(`[AudioMetadataManager] Write chapters.txt failed`, error) | ||||
|         chaptersFilePath = null | ||||
|       Logger.error(`[AudioMetadataManager] Write metadata.json failed`, error) | ||||
|       toneJsonPath = null | ||||
|     } | ||||
|     } | ||||
| 
 | ||||
|     const toneMetadataObject = toneHelpers.getToneMetadataObject(libraryItem, chaptersFilePath) | ||||
|     Logger.debug(`[AudioMetadataManager] Book "${libraryItem.media.metadata.title}" tone metadata object=`, toneMetadataObject) | ||||
| 
 | ||||
|     const results = [] | ||||
|     for (const af of audioFiles) { | ||||
|       const result = await this.updateAudioFileMetadataWithTone(libraryItem.id, af, toneMetadataObject, itemCacheDir) | ||||
|       const result = await this.updateAudioFileMetadataWithTone(libraryItem.id, af, toneJsonPath, itemCacheDir) | ||||
|       results.push(result) | ||||
|     } | ||||
| 
 | ||||
| @ -74,7 +70,7 @@ class AudioMetadataMangaer { | ||||
|     this.emitter('audio_metadata_finished', itemAudioMetadataPayload) | ||||
|   } | ||||
| 
 | ||||
|   async updateAudioFileMetadataWithTone(libraryItemId, audioFile, toneMetadataObject, itemCacheDir) { | ||||
|   async updateAudioFileMetadataWithTone(libraryItemId, audioFile, toneJsonPath, itemCacheDir) { | ||||
|     const resultPayload = { | ||||
|       libraryItemId, | ||||
|       index: audioFile.index, | ||||
| @ -93,8 +89,8 @@ class AudioMetadataMangaer { | ||||
|     } | ||||
| 
 | ||||
|     const _toneMetadataObject = { | ||||
|       ...toneMetadataObject, | ||||
|       'TrackNumber': audioFile.index | ||||
|       'ToneJsonFile': toneJsonPath, | ||||
|       'TrackNumber': audioFile.index, | ||||
|     } | ||||
| 
 | ||||
|     resultPayload.success = await toneHelpers.tagAudioFile(audioFile.metadata.path, _toneMetadataObject) | ||||
|  | ||||
| @ -72,6 +72,70 @@ module.exports.getToneMetadataObject = (libraryItem, chaptersFile) => { | ||||
|   return metadataObject | ||||
| } | ||||
| 
 | ||||
| module.exports.writeToneMetadataJsonFile = (libraryItem, chapters, filePath, trackTotal) => { | ||||
|   const bookMetadata = libraryItem.media.metadata | ||||
|   const coverPath = libraryItem.media.coverPath | ||||
| 
 | ||||
|   const metadataObject = { | ||||
|     'album': bookMetadata.title || '', | ||||
|     'title': bookMetadata.title || '', | ||||
|     'trackTotal': trackTotal, | ||||
|     'additionalFields': {} | ||||
|   } | ||||
|   if (bookMetadata.subtitle) { | ||||
|     metadataObject['subtitle'] = bookMetadata.subtitle | ||||
|   } | ||||
|   if (bookMetadata.authorName) { | ||||
|     metadataObject['artist'] = bookMetadata.authorName | ||||
|     metadataObject['albumArtist'] = bookMetadata.authorName | ||||
|   } | ||||
|   if (bookMetadata.description) { | ||||
|     metadataObject['comment'] = bookMetadata.description | ||||
|     metadataObject['description'] = bookMetadata.description | ||||
|   } | ||||
|   if (bookMetadata.narratorName) { | ||||
|     metadataObject['narrator'] = bookMetadata.narratorName | ||||
|     metadataObject['composer'] = bookMetadata.narratorName | ||||
|   } | ||||
|   if (bookMetadata.firstSeriesName) { | ||||
|     metadataObject['movementName'] = bookMetadata.firstSeriesName | ||||
|   } | ||||
|   if (bookMetadata.firstSeriesSequence) { | ||||
|     metadataObject['movement'] = bookMetadata.firstSeriesSequence | ||||
|   } | ||||
|   if (bookMetadata.genres.length) { | ||||
|     metadataObject['genre'] = bookMetadata.genres.join('/') | ||||
|   } | ||||
|   if (bookMetadata.publisher) { | ||||
|     metadataObject['publisher'] = bookMetadata.publisher | ||||
|   } | ||||
|   if (bookMetadata.asin) { | ||||
|     metadataObject.additionalFields['asin'] = bookMetadata.asin | ||||
|   } | ||||
|   if (bookMetadata.isbn) { | ||||
|     metadataObject.additionalFields['isbn'] = bookMetadata.isbn | ||||
|   } | ||||
|   if (coverPath) { | ||||
|     metadataObject['coverFile'] = coverPath | ||||
|   } | ||||
|   if (parsePublishedYear(bookMetadata.publishedYear)) { | ||||
|     metadataObject['publishingDate'] = parsePublishedYear(bookMetadata.publishedYear) | ||||
|   } | ||||
|   if (chapters && chapters.length > 0) { | ||||
|     let metadataChapters = [] | ||||
|     for (const chapter of chapters) { | ||||
|       metadataChapters.push({ | ||||
|         start: Math.round(chapter.start * 1000), | ||||
|         length: Math.round((chapter.end - chapter.start) * 1000), | ||||
|         title: chapter.title, | ||||
|       }) | ||||
|     } | ||||
|     metadataObject['chapters'] = metadataChapters | ||||
|   } | ||||
| 
 | ||||
|   return fs.writeFile(filePath, JSON.stringify({ meta: metadataObject }, null, 2)) | ||||
| } | ||||
| 
 | ||||
| module.exports.tagAudioFile = (filePath, payload) => { | ||||
|   return tone.tag(filePath, payload).then((data) => { | ||||
|     return true | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user