From c1938f78c24feff8d81cf7bb4b7c17a714d8ad79 Mon Sep 17 00:00:00 2001
From: Keagan Hilliard
Date: Wed, 2 Nov 2022 19:40:50 -0600
Subject: [PATCH 1/5] Added json file support
---
server/managers/AbMergeManager.js | 22 +++++----
server/managers/AudioMetadataManager.js | 25 ++++------
server/utils/toneHelpers.js | 65 +++++++++++++++++++++++++
3 files changed, 87 insertions(+), 25 deletions(-)
diff --git a/server/managers/AbMergeManager.js b/server/managers/AbMergeManager.js
index 1b3801c0..25e571e2 100644
--- a/server/managers/AbMergeManager.js
+++ b/server/managers/AbMergeManager.js
@@ -120,20 +120,22 @@ class AbMergeManager {
}
}
- var chaptersFilePath = null
- if (libraryItem.media.chapters.length) {
- chaptersFilePath = Path.join(task.data.itemCachePath, 'chapters.txt')
- try {
- await toneHelpers.writeToneChaptersFile(libraryItem.media.chapters, chaptersFilePath)
- } catch (error) {
- Logger.error(`[AbMergeManager] Write chapters.txt failed`, error)
- chaptersFilePath = null
- }
+ var toneJsonPath = null
+ try {
+ toneJsonPath = Path.join(itemCacheDir, 'metadata.json')
+ await toneHelpers.writeToneMetadataJsonFile(libraryItem, toneJsonPath)
+ } catch (error) {
+ Logger.error(`[AudioMetadataManager] Write metadata.json failed`, error)
+ toneJsonPath = null
}
const toneMetadataObject = toneHelpers.getToneMetadataObject(libraryItem, chaptersFilePath)
toneMetadataObject.TrackNumber = 1
task.data.toneMetadataObject = toneMetadataObject
+ task.data.toneJsonObject = {
+ 'ToneJsonFile': toneJsonPath,
+ 'TrackNumber': 1,
+ }
Logger.debug(`[AbMergeManager] Book "${libraryItem.media.metadata.title}" tone metadata object=`, toneMetadataObject)
@@ -190,7 +192,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')
diff --git a/server/managers/AudioMetadataManager.js b/server/managers/AudioMetadataManager.js
index 02a44ce1..bcf51aed 100644
--- a/server/managers/AudioMetadataManager.js
+++ b/server/managers/AudioMetadataManager.js
@@ -43,23 +43,18 @@ 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)
- } catch (error) {
- Logger.error(`[AudioMetadataManager] Write chapters.txt failed`, error)
- chaptersFilePath = null
- }
+ try {
+ toneJsonPath = Path.join(itemCacheDir, 'metadata.json')
+ await toneHelpers.writeToneMetadataJsonFile(libraryItem, toneJsonPath)
+ } catch (error) {
+ 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)
@@ -74,7 +69,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 +88,8 @@ class AudioMetadataMangaer {
}
const _toneMetadataObject = {
- ...toneMetadataObject,
- 'TrackNumber': audioFile.index
+ 'ToneJsonFile': toneJsonPath,
+ 'TrackNumber': audioFile.index,
}
resultPayload.success = await toneHelpers.tagAudioFile(audioFile.metadata.path, _toneMetadataObject)
diff --git a/server/utils/toneHelpers.js b/server/utils/toneHelpers.js
index fc88c169..54add6bd 100644
--- a/server/utils/toneHelpers.js
+++ b/server/utils/toneHelpers.js
@@ -72,6 +72,71 @@ module.exports.getToneMetadataObject = (libraryItem, chaptersFile) => {
return metadataObject
}
+module.exports.writeToneMetadataJsonFile = (libraryItem, filePath) => {
+ const bookMetadata = libraryItem.media.metadata
+ const coverPath = libraryItem.media.coverPath
+ const chapters = libraryItem.media.chapters
+
+ const metadataObject = {
+ 'album': bookMetadata.title || '',
+ 'title': bookMetadata.title || '',
+ 'trackTotal': libraryItem.media.tracks.length,
+ '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: chapter.start,
+ length: chapter.end - chapter.start,
+ title: chapter.title,
+ })
+ }
+ metadataObject['chapters'] = chaptersFile
+ }
+
+ return fs.writeFile(filePath, JSON.stringify({ metadata: metadataObject }))
+}
+
module.exports.tagAudioFile = (filePath, payload) => {
return tone.tag(filePath, payload).then((data) => {
return true
From d57effe97cb6dbee7ad7a28a4688bc85aa4e5554 Mon Sep 17 00:00:00 2001
From: Keagan Hilliard
Date: Thu, 3 Nov 2022 09:32:50 -0600
Subject: [PATCH 2/5] Fixed a couple of issues, should be working well now
---
server/managers/AudioMetadataManager.js | 2 +-
server/utils/toneHelpers.js | 9 +++++----
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/server/managers/AudioMetadataManager.js b/server/managers/AudioMetadataManager.js
index bcf51aed..88bb83e9 100644
--- a/server/managers/AudioMetadataManager.js
+++ b/server/managers/AudioMetadataManager.js
@@ -57,7 +57,7 @@ class AudioMetadataMangaer {
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)
}
diff --git a/server/utils/toneHelpers.js b/server/utils/toneHelpers.js
index 54add6bd..ff57bf1e 100644
--- a/server/utils/toneHelpers.js
+++ b/server/utils/toneHelpers.js
@@ -126,19 +126,20 @@ module.exports.writeToneMetadataJsonFile = (libraryItem, filePath) => {
let metadataChapters = []
for (const chapter of chapters) {
metadataChapters.push({
- start: chapter.start,
- length: chapter.end - chapter.start,
+ start: Math.round(chapter.start * 1000),
+ length: Math.round((chapter.end - chapter.start) * 1000),
title: chapter.title,
})
}
- metadataObject['chapters'] = chaptersFile
+ metadataObject['chapters'] = metadataChapters
}
- return fs.writeFile(filePath, JSON.stringify({ metadata: metadataObject }))
+ return fs.writeFile(filePath, JSON.stringify({ meta: metadataObject }))
}
module.exports.tagAudioFile = (filePath, payload) => {
return tone.tag(filePath, payload).then((data) => {
+ Logger.info('Tone results: ', data)
return true
}).catch((error) => {
Logger.error(`[toneHelpers] tagAudioFile: Failed for "${filePath}"`, error)
From 586c8a550a9b53487aabd13fa47d44626cf1da09 Mon Sep 17 00:00:00 2001
From: Keagan Hilliard
Date: Thu, 3 Nov 2022 10:09:49 -0600
Subject: [PATCH 3/5] Removed a noisy log and limit chapter embedding to items
with only 1 audiofile
---
server/managers/AudioMetadataManager.js | 2 +-
server/utils/toneHelpers.js | 4 +---
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/server/managers/AudioMetadataManager.js b/server/managers/AudioMetadataManager.js
index 88bb83e9..c76e3fe2 100644
--- a/server/managers/AudioMetadataManager.js
+++ b/server/managers/AudioMetadataManager.js
@@ -49,7 +49,7 @@ class AudioMetadataMangaer {
try {
toneJsonPath = Path.join(itemCacheDir, 'metadata.json')
- await toneHelpers.writeToneMetadataJsonFile(libraryItem, toneJsonPath)
+ await toneHelpers.writeToneMetadataJsonFile(libraryItem, audioFiles.length == 1 && libraryItem.media.chapters, toneJsonPath)
} catch (error) {
Logger.error(`[AudioMetadataManager] Write metadata.json failed`, error)
toneJsonPath = null
diff --git a/server/utils/toneHelpers.js b/server/utils/toneHelpers.js
index ff57bf1e..fcdd1440 100644
--- a/server/utils/toneHelpers.js
+++ b/server/utils/toneHelpers.js
@@ -72,10 +72,9 @@ module.exports.getToneMetadataObject = (libraryItem, chaptersFile) => {
return metadataObject
}
-module.exports.writeToneMetadataJsonFile = (libraryItem, filePath) => {
+module.exports.writeToneMetadataJsonFile = (libraryItem, chapters, filePath) => {
const bookMetadata = libraryItem.media.metadata
const coverPath = libraryItem.media.coverPath
- const chapters = libraryItem.media.chapters
const metadataObject = {
'album': bookMetadata.title || '',
@@ -139,7 +138,6 @@ module.exports.writeToneMetadataJsonFile = (libraryItem, filePath) => {
module.exports.tagAudioFile = (filePath, payload) => {
return tone.tag(filePath, payload).then((data) => {
- Logger.info('Tone results: ', data)
return true
}).catch((error) => {
Logger.error(`[toneHelpers] tagAudioFile: Failed for "${filePath}"`, error)
From 3824154c15fa6f86d121ef8db5da77c2c3154079 Mon Sep 17 00:00:00 2001
From: Keagan Hilliard
Date: Thu, 3 Nov 2022 10:20:32 -0600
Subject: [PATCH 4/5] Forgot to update the merge
---
server/managers/AbMergeManager.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/managers/AbMergeManager.js b/server/managers/AbMergeManager.js
index 25e571e2..0bc89be3 100644
--- a/server/managers/AbMergeManager.js
+++ b/server/managers/AbMergeManager.js
@@ -123,7 +123,7 @@ class AbMergeManager {
var toneJsonPath = null
try {
toneJsonPath = Path.join(itemCacheDir, 'metadata.json')
- await toneHelpers.writeToneMetadataJsonFile(libraryItem, toneJsonPath)
+ await toneHelpers.writeToneMetadataJsonFile(libraryItem, libraryItem.media.chapters, toneJsonPath)
} catch (error) {
Logger.error(`[AudioMetadataManager] Write metadata.json failed`, error)
toneJsonPath = null
From 1118b8b78200704d2c16c5c36f7fccc70e5d0f4f Mon Sep 17 00:00:00 2001
From: advplyr
Date: Sat, 5 Nov 2022 13:13:52 -0500
Subject: [PATCH 5/5] Metadata embed and m4b merge fixes and cleanup
---
client/pages/audiobook/_id/manage.vue | 4 ++++
server/controllers/LibraryItemController.js | 3 ++-
server/managers/AbMergeManager.js | 13 ++++---------
server/managers/AudioMetadataManager.js | 9 +++++----
server/utils/toneHelpers.js | 6 +++---
5 files changed, 18 insertions(+), 17 deletions(-)
diff --git a/client/pages/audiobook/_id/manage.vue b/client/pages/audiobook/_id/manage.vue
index 9f858a81..a45979dc 100644
--- a/client/pages/audiobook/_id/manage.vue
+++ b/client/pages/audiobook/_id/manage.vue
@@ -91,6 +91,10 @@
A backup of your original audio files will be stored in /metadata/cache/items/{{ libraryItemId }}/. Make sure to periodically purge items cache.
+
+
star
+
Chapters are not embedded in multi-track audiobooks.
+
star
Encoding can take up to 30 minutes.
diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js
index b1f3f77d..56b181c2 100644
--- a/server/controllers/LibraryItemController.js
+++ b/server/controllers/LibraryItemController.js
@@ -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)
}
diff --git a/server/managers/AbMergeManager.js b/server/managers/AbMergeManager.js
index 0bc89be3..72343d50 100644
--- a/server/managers/AbMergeManager.js
+++ b/server/managers/AbMergeManager.js
@@ -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)
@@ -122,23 +122,18 @@ class AbMergeManager {
var toneJsonPath = null
try {
- toneJsonPath = Path.join(itemCacheDir, 'metadata.json')
- await toneHelpers.writeToneMetadataJsonFile(libraryItem, libraryItem.media.chapters, toneJsonPath)
+ toneJsonPath = Path.join(task.data.itemCachePath, 'metadata.json')
+ await toneHelpers.writeToneMetadataJsonFile(libraryItem, libraryItem.media.chapters, toneJsonPath, 1)
} catch (error) {
- Logger.error(`[AudioMetadataManager] Write metadata.json failed`, error)
+ Logger.error(`[AbMergeManager] Write metadata.json failed`, error)
toneJsonPath = null
}
- const toneMetadataObject = toneHelpers.getToneMetadataObject(libraryItem, chaptersFilePath)
- toneMetadataObject.TrackNumber = 1
- task.data.toneMetadataObject = toneMetadataObject
task.data.toneJsonObject = {
'ToneJsonFile': toneJsonPath,
'TrackNumber': 1,
}
- Logger.debug(`[AbMergeManager] Book "${libraryItem.media.metadata.title}" tone metadata object=`, toneMetadataObject)
-
var workerData = {
inputs: ffmpegInputs,
options: ffmpegOptions,
diff --git a/server/managers/AudioMetadataManager.js b/server/managers/AudioMetadataManager.js
index c76e3fe2..25a93583 100644
--- a/server/managers/AudioMetadataManager.js
+++ b/server/managers/AudioMetadataManager.js
@@ -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 = {
@@ -49,7 +49,8 @@ class AudioMetadataMangaer {
try {
toneJsonPath = Path.join(itemCacheDir, 'metadata.json')
- await toneHelpers.writeToneMetadataJsonFile(libraryItem, audioFiles.length == 1 && libraryItem.media.chapters, toneJsonPath)
+ 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)
toneJsonPath = null
diff --git a/server/utils/toneHelpers.js b/server/utils/toneHelpers.js
index fcdd1440..dac6fa40 100644
--- a/server/utils/toneHelpers.js
+++ b/server/utils/toneHelpers.js
@@ -72,14 +72,14 @@ module.exports.getToneMetadataObject = (libraryItem, chaptersFile) => {
return metadataObject
}
-module.exports.writeToneMetadataJsonFile = (libraryItem, chapters, filePath) => {
+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': libraryItem.media.tracks.length,
+ 'trackTotal': trackTotal,
'additionalFields': {}
}
if (bookMetadata.subtitle) {
@@ -133,7 +133,7 @@ module.exports.writeToneMetadataJsonFile = (libraryItem, chapters, filePath) =>
metadataObject['chapters'] = metadataChapters
}
- return fs.writeFile(filePath, JSON.stringify({ meta: metadataObject }))
+ return fs.writeFile(filePath, JSON.stringify({ meta: metadataObject }, null, 2))
}
module.exports.tagAudioFile = (filePath, payload) => {