mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +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