mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-02-14 01:10:38 +01:00
Remove tone scripts & references, rename tone-object endpoint, remove node-tone dependency, remove TONE_PATH env
This commit is contained in:
parent
8bdee51798
commit
e6b1acfb44
@ -10,6 +10,3 @@ RUN apt-get update && \
|
|||||||
DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \
|
DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \
|
||||||
curl tzdata ffmpeg && \
|
curl tzdata ffmpeg && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Move tone executable to appropriate directory
|
|
||||||
COPY --from=sandreas/tone:v0.1.5 /usr/local/bin/tone /usr/local/bin/
|
|
||||||
|
@ -6,7 +6,6 @@ RUN npm ci && npm cache clean --force
|
|||||||
RUN npm run generate
|
RUN npm run generate
|
||||||
|
|
||||||
### STAGE 1: Build server ###
|
### STAGE 1: Build server ###
|
||||||
FROM sandreas/tone:v0.1.5 AS tone
|
|
||||||
FROM node:20-alpine
|
FROM node:20-alpine
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
@ -21,7 +20,6 @@ RUN apk update && \
|
|||||||
g++ \
|
g++ \
|
||||||
tini
|
tini
|
||||||
|
|
||||||
COPY --from=tone /usr/local/bin/tone /usr/local/bin/
|
|
||||||
COPY --from=build /client/dist /client/dist
|
COPY --from=build /client/dist /client/dist
|
||||||
COPY index.js package* /
|
COPY index.js package* /
|
||||||
COPY server server
|
COPY server server
|
||||||
|
@ -50,7 +50,6 @@ install_ffmpeg() {
|
|||||||
echo "Starting FFMPEG Install"
|
echo "Starting FFMPEG Install"
|
||||||
|
|
||||||
WGET="wget https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz --output-document=ffmpeg-git-amd64-static.tar.xz"
|
WGET="wget https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz --output-document=ffmpeg-git-amd64-static.tar.xz"
|
||||||
WGET_TONE="wget https://github.com/sandreas/tone/releases/download/v0.1.5/tone-0.1.5-linux-x64.tar.gz --output-document=tone-0.1.5-linux-x64.tar.gz"
|
|
||||||
|
|
||||||
if ! cd "$FFMPEG_INSTALL_DIR"; then
|
if ! cd "$FFMPEG_INSTALL_DIR"; then
|
||||||
echo "Creating ffmpeg install dir at $FFMPEG_INSTALL_DIR"
|
echo "Creating ffmpeg install dir at $FFMPEG_INSTALL_DIR"
|
||||||
@ -63,13 +62,7 @@ install_ffmpeg() {
|
|||||||
tar xvf ffmpeg-git-amd64-static.tar.xz --strip-components=1 --no-same-owner
|
tar xvf ffmpeg-git-amd64-static.tar.xz --strip-components=1 --no-same-owner
|
||||||
rm ffmpeg-git-amd64-static.tar.xz
|
rm ffmpeg-git-amd64-static.tar.xz
|
||||||
|
|
||||||
# Temp downloading tone library to the ffmpeg dir
|
echo "Good to go on Ffmpeg... hopefully"
|
||||||
echo "Getting tone.."
|
|
||||||
$WGET_TONE
|
|
||||||
tar xvf tone-0.1.5-linux-x64.tar.gz --strip-components=1 --no-same-owner
|
|
||||||
rm tone-0.1.5-linux-x64.tar.gz
|
|
||||||
|
|
||||||
echo "Good to go on Ffmpeg (& tone)... hopefully"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_config() {
|
setup_config() {
|
||||||
@ -77,12 +70,6 @@ setup_config() {
|
|||||||
echo "Existing config found."
|
echo "Existing config found."
|
||||||
cat $CONFIG_PATH
|
cat $CONFIG_PATH
|
||||||
|
|
||||||
# TONE_PATH variable added in 2.1.6, if it doesnt exist then add it
|
|
||||||
if ! grep -q "TONE_PATH" "$CONFIG_PATH"; then
|
|
||||||
echo "Adding TONE_PATH to existing config"
|
|
||||||
echo "TONE_PATH=$FFMPEG_INSTALL_DIR/tone" >> "$CONFIG_PATH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
if [ ! -d "$DEFAULT_DATA_DIR" ]; then
|
if [ ! -d "$DEFAULT_DATA_DIR" ]; then
|
||||||
@ -98,7 +85,6 @@ setup_config() {
|
|||||||
CONFIG_PATH=$DEFAULT_DATA_DIR/config
|
CONFIG_PATH=$DEFAULT_DATA_DIR/config
|
||||||
FFMPEG_PATH=$FFMPEG_INSTALL_DIR/ffmpeg
|
FFMPEG_PATH=$FFMPEG_INSTALL_DIR/ffmpeg
|
||||||
FFPROBE_PATH=$FFMPEG_INSTALL_DIR/ffprobe
|
FFPROBE_PATH=$FFMPEG_INSTALL_DIR/ffprobe
|
||||||
TONE_PATH=$FFMPEG_INSTALL_DIR/tone
|
|
||||||
PORT=$DEFAULT_PORT
|
PORT=$DEFAULT_PORT
|
||||||
HOST=$DEFAULT_HOST"
|
HOST=$DEFAULT_HOST"
|
||||||
|
|
||||||
|
@ -11,10 +11,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center mb-2">
|
||||||
<div class="w-full max-w-2xl">
|
<div class="w-full max-w-2xl">
|
||||||
<p class="text-xl mb-1">{{ $strings.HeaderMetadataToEmbed }}</p>
|
<p class="text-xl">{{ $strings.HeaderMetadataToEmbed }}</p>
|
||||||
<p class="mb-2 text-base text-gray-300">audiobookshelf uses <a href="https://github.com/sandreas/tone" target="_blank" class="hover:underline text-blue-400 hover:text-blue-300">tone</a> to write metadata.</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full max-w-2xl"></div>
|
<div class="w-full max-w-2xl"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -26,7 +25,7 @@
|
|||||||
<div class="w-2/3 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelValue }}</div>
|
<div class="w-2/3 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelValue }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full max-h-72 overflow-auto">
|
<div class="w-full max-h-72 overflow-auto">
|
||||||
<template v-for="(value, key, index) in toneObject">
|
<template v-for="(value, key, index) in metadataObject">
|
||||||
<div :key="key" class="flex py-1 px-4 text-sm" :class="index % 2 === 0 ? 'bg-primary bg-opacity-25' : ''">
|
<div :key="key" class="flex py-1 px-4 text-sm" :class="index % 2 === 0 ? 'bg-primary bg-opacity-25' : ''">
|
||||||
<div class="w-1/3 font-semibold">{{ key }}</div>
|
<div class="w-1/3 font-semibold">{{ key }}</div>
|
||||||
<div class="w-2/3">
|
<div class="w-2/3">
|
||||||
@ -208,7 +207,7 @@ export default {
|
|||||||
processing: false,
|
processing: false,
|
||||||
audiofilesEncoding: {},
|
audiofilesEncoding: {},
|
||||||
audiofilesFinished: {},
|
audiofilesFinished: {},
|
||||||
toneObject: null,
|
metadataObject: null,
|
||||||
selectedTool: 'embed',
|
selectedTool: 'embed',
|
||||||
isCancelingEncode: false,
|
isCancelingEncode: false,
|
||||||
showEncodeOptions: false,
|
showEncodeOptions: false,
|
||||||
@ -387,7 +386,7 @@ export default {
|
|||||||
window.history.replaceState({ path: newurl }, '', newurl)
|
window.history.replaceState({ path: newurl }, '', newurl)
|
||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
this.fetchToneObject()
|
this.fetchMetadataEmbedObject()
|
||||||
if (this.$route.query.tool === 'm4b') {
|
if (this.$route.query.tool === 'm4b') {
|
||||||
if (this.availableTools.some((t) => t.value === 'm4b')) {
|
if (this.availableTools.some((t) => t.value === 'm4b')) {
|
||||||
this.selectedTool = 'm4b'
|
this.selectedTool = 'm4b'
|
||||||
@ -401,15 +400,14 @@ export default {
|
|||||||
const shouldBackupAudioFiles = localStorage.getItem('embedMetadataShouldBackup')
|
const shouldBackupAudioFiles = localStorage.getItem('embedMetadataShouldBackup')
|
||||||
this.shouldBackupAudioFiles = shouldBackupAudioFiles != 0
|
this.shouldBackupAudioFiles = shouldBackupAudioFiles != 0
|
||||||
},
|
},
|
||||||
fetchToneObject() {
|
fetchMetadataEmbedObject() {
|
||||||
this.$axios
|
this.$axios
|
||||||
.$get(`/api/items/${this.libraryItemId}/tone-object`)
|
.$get(`/api/items/${this.libraryItemId}/metadata-object`)
|
||||||
.then((toneObject) => {
|
.then((metadataObject) => {
|
||||||
delete toneObject.CoverFile
|
this.metadataObject = metadataObject
|
||||||
this.toneObject = toneObject
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to fetch tone object', error)
|
console.error('Failed to fetch metadata object', error)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
taskUpdated(task) {
|
taskUpdated(task) {
|
||||||
@ -426,4 +424,4 @@ export default {
|
|||||||
this.$root.socket.off('audiofile_metadata_finished', this.audiofileMetadataFinished)
|
this.$root.socket.off('audiofile_metadata_finished', this.audiofileMetadataFinished)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -16,7 +16,6 @@
|
|||||||
"graceful-fs": "^4.2.10",
|
"graceful-fs": "^4.2.10",
|
||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^8.0.1",
|
||||||
"lru-cache": "^10.0.3",
|
"lru-cache": "^10.0.3",
|
||||||
"node-tone": "^1.0.1",
|
|
||||||
"nodemailer": "^6.9.13",
|
"nodemailer": "^6.9.13",
|
||||||
"openid-client": "^5.6.1",
|
"openid-client": "^5.6.1",
|
||||||
"p-throttle": "^4.1.1",
|
"p-throttle": "^4.1.1",
|
||||||
@ -3661,11 +3660,6 @@
|
|||||||
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
|
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/node-tone": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-tone/-/node-tone-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-wi7L0taDZMN6tM5l85TDKHsYzdhqJTtPNgvgpk2zHeZzPt6ZIUZ9vBLTJRRDpm0xzCvbsvFHjAaudeQjLHTE4w=="
|
|
||||||
},
|
|
||||||
"node_modules/nodemailer": {
|
"node_modules/nodemailer": {
|
||||||
"version": "6.9.13",
|
"version": "6.9.13",
|
||||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz",
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz",
|
||||||
|
@ -42,7 +42,6 @@
|
|||||||
"graceful-fs": "^4.2.10",
|
"graceful-fs": "^4.2.10",
|
||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^8.0.1",
|
||||||
"lru-cache": "^10.0.3",
|
"lru-cache": "^10.0.3",
|
||||||
"node-tone": "^1.0.1",
|
|
||||||
"nodemailer": "^6.9.13",
|
"nodemailer": "^6.9.13",
|
||||||
"openid-client": "^5.6.1",
|
"openid-client": "^5.6.1",
|
||||||
"p-throttle": "^4.1.1",
|
"p-throttle": "^4.1.1",
|
||||||
|
@ -31,7 +31,7 @@ Audiobookshelf is a self-hosted audiobook and podcast server.
|
|||||||
- Fetch metadata and cover art from several sources
|
- Fetch metadata and cover art from several sources
|
||||||
- Chapter editor and chapter lookup (using [Audnexus API](https://audnex.us/))
|
- Chapter editor and chapter lookup (using [Audnexus API](https://audnex.us/))
|
||||||
- Merge your audio files into a single m4b
|
- Merge your audio files into a single m4b
|
||||||
- Embed metadata and cover image into your audio files (using [Tone](https://github.com/sandreas/tone))
|
- Embed metadata and cover image into your audio files
|
||||||
- Basic ebook support and ereader
|
- Basic ebook support and ereader
|
||||||
- Epub, pdf, cbr, cbz
|
- Epub, pdf, cbr, cbz
|
||||||
- Send ebook to device (i.e. Kindle)
|
- Send ebook to device (i.e. Kindle)
|
||||||
|
@ -559,9 +559,9 @@ class LibraryItemController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getToneMetadataObject(req, res) {
|
getMetadataObject(req, res) {
|
||||||
if (!req.user.isAdminOrUp) {
|
if (!req.user.isAdminOrUp) {
|
||||||
Logger.error(`[LibraryItemController] Non-admin user attempted to get tone metadata object`, req.user)
|
Logger.error(`[LibraryItemController] Non-admin user attempted to get metadata object`, req.user)
|
||||||
return res.sendStatus(403)
|
return res.sendStatus(403)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,7 +570,7 @@ class LibraryItemController {
|
|||||||
return res.sendStatus(500)
|
return res.sendStatus(500)
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(this.audioMetadataManager.getToneMetadataObjectForApi(req.libraryItem))
|
res.json(this.audioMetadataManager.getMetadataObjectForApi(req.libraryItem))
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: api/items/:id/chapters
|
// POST: api/items/:id/chapters
|
||||||
|
@ -32,7 +32,7 @@ class AudioMetadataMangaer {
|
|||||||
return this.tasksQueued.some((t) => t.data.libraryItemId === libraryItemId) || this.tasksRunning.some((t) => t.data.libraryItemId === libraryItemId)
|
return this.tasksQueued.some((t) => t.data.libraryItemId === libraryItemId) || this.tasksRunning.some((t) => t.data.libraryItemId === libraryItemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
getToneMetadataObjectForApi(libraryItem) {
|
getMetadataObjectForApi(libraryItem) {
|
||||||
return ffmpegHelpers.getFFMetadataObject(libraryItem, libraryItem.media.includedAudioFiles.length)
|
return ffmpegHelpers.getFFMetadataObject(libraryItem, libraryItem.media.includedAudioFiles.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ class AudioMetaTags {
|
|||||||
|
|
||||||
// Track ID3 tag might be "3/10" or just "3"
|
// Track ID3 tag might be "3/10" or just "3"
|
||||||
if (this.tagTrack) {
|
if (this.tagTrack) {
|
||||||
const trackParts = this.tagTrack.split('/').map(part => Number(part))
|
const trackParts = this.tagTrack.split('/').map((part) => Number(part))
|
||||||
if (trackParts.length > 0) {
|
if (trackParts.length > 0) {
|
||||||
// Fractional track numbers not supported
|
// Fractional track numbers not supported
|
||||||
data.number = !isNaN(trackParts[0]) ? Math.trunc(trackParts[0]) : null
|
data.number = !isNaN(trackParts[0]) ? Math.trunc(trackParts[0]) : null
|
||||||
@ -81,7 +81,7 @@ class AudioMetaTags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.tagDisc) {
|
if (this.tagDisc) {
|
||||||
const discParts = this.tagDisc.split('/').map(p => Number(p))
|
const discParts = this.tagDisc.split('/').map((p) => Number(p))
|
||||||
if (discParts.length > 0) {
|
if (discParts.length > 0) {
|
||||||
data.number = !isNaN(discParts[0]) ? Math.trunc(discParts[0]) : null
|
data.number = !isNaN(discParts[0]) ? Math.trunc(discParts[0]) : null
|
||||||
}
|
}
|
||||||
@ -93,10 +93,18 @@ class AudioMetaTags {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
get discNumber() { return this.discNumAndTotal.number }
|
get discNumber() {
|
||||||
get discTotal() { return this.discNumAndTotal.total }
|
return this.discNumAndTotal.number
|
||||||
get trackNumber() { return this.trackNumAndTotal.number }
|
}
|
||||||
get trackTotal() { return this.trackNumAndTotal.total }
|
get discTotal() {
|
||||||
|
return this.discNumAndTotal.total
|
||||||
|
}
|
||||||
|
get trackNumber() {
|
||||||
|
return this.trackNumAndTotal.number
|
||||||
|
}
|
||||||
|
get trackTotal() {
|
||||||
|
return this.trackNumAndTotal.total
|
||||||
|
}
|
||||||
|
|
||||||
construct(metadata) {
|
construct(metadata) {
|
||||||
this.tagAlbum = metadata.tagAlbum || null
|
this.tagAlbum = metadata.tagAlbum || null
|
||||||
@ -177,10 +185,6 @@ class AudioMetaTags {
|
|||||||
this.tagMusicBrainzArtistId = payload.file_tag_musicbrainz_artistid || null
|
this.tagMusicBrainzArtistId = payload.file_tag_musicbrainz_artistid || null
|
||||||
}
|
}
|
||||||
|
|
||||||
setDataFromTone(tags) {
|
|
||||||
// TODO: Implement
|
|
||||||
}
|
|
||||||
|
|
||||||
updateData(payload) {
|
updateData(payload) {
|
||||||
const dataMap = {
|
const dataMap = {
|
||||||
tagAlbum: payload.file_tag_album || null,
|
tagAlbum: payload.file_tag_album || null,
|
||||||
@ -243,4 +247,4 @@ class AudioMetaTags {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = AudioMetaTags
|
module.exports = AudioMetaTags
|
||||||
|
@ -114,7 +114,7 @@ class ApiRouter {
|
|||||||
this.router.post('/items/:id/play/:episodeId', LibraryItemController.middleware.bind(this), LibraryItemController.startEpisodePlaybackSession.bind(this))
|
this.router.post('/items/:id/play/:episodeId', LibraryItemController.middleware.bind(this), LibraryItemController.startEpisodePlaybackSession.bind(this))
|
||||||
this.router.patch('/items/:id/tracks', LibraryItemController.middleware.bind(this), LibraryItemController.updateTracks.bind(this))
|
this.router.patch('/items/:id/tracks', LibraryItemController.middleware.bind(this), LibraryItemController.updateTracks.bind(this))
|
||||||
this.router.post('/items/:id/scan', LibraryItemController.middleware.bind(this), LibraryItemController.scan.bind(this))
|
this.router.post('/items/:id/scan', LibraryItemController.middleware.bind(this), LibraryItemController.scan.bind(this))
|
||||||
this.router.get('/items/:id/tone-object', LibraryItemController.middleware.bind(this), LibraryItemController.getToneMetadataObject.bind(this))
|
this.router.get('/items/:id/metadata-object', LibraryItemController.middleware.bind(this), LibraryItemController.getMetadataObject.bind(this))
|
||||||
this.router.post('/items/:id/chapters', LibraryItemController.middleware.bind(this), LibraryItemController.updateMediaChapters.bind(this))
|
this.router.post('/items/:id/chapters', LibraryItemController.middleware.bind(this), LibraryItemController.updateMediaChapters.bind(this))
|
||||||
this.router.get('/items/:id/ffprobe/:fileid', LibraryItemController.middleware.bind(this), LibraryItemController.getFFprobeData.bind(this))
|
this.router.get('/items/:id/ffprobe/:fileid', LibraryItemController.middleware.bind(this), LibraryItemController.getFFprobeData.bind(this))
|
||||||
this.router.get('/items/:id/file/:fileid', LibraryItemController.middleware.bind(this), LibraryItemController.getLibraryFile.bind(this))
|
this.router.get('/items/:id/file/:fileid', LibraryItemController.middleware.bind(this), LibraryItemController.getLibraryFile.bind(this))
|
||||||
|
@ -63,15 +63,5 @@ class MediaProbeData {
|
|||||||
this.audioMetaTags = new AudioMetaTags()
|
this.audioMetaTags = new AudioMetaTags()
|
||||||
this.audioMetaTags.setData(data.tags)
|
this.audioMetaTags.setData(data.tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
setDataFromTone(data) {
|
|
||||||
// TODO: Implement
|
|
||||||
|
|
||||||
this.format = data.format
|
|
||||||
this.duration = data.duration
|
|
||||||
this.size = data.size
|
|
||||||
this.audioMetaTags = new AudioMetaTags()
|
|
||||||
this.audioMetaTags.setDataFromTone(data.tags)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
module.exports = MediaProbeData
|
module.exports = MediaProbeData
|
||||||
|
@ -1,113 +0,0 @@
|
|||||||
const tone = require('node-tone')
|
|
||||||
const fs = require('../libs/fsExtra')
|
|
||||||
const Logger = require('../Logger')
|
|
||||||
|
|
||||||
function getToneMetadataObject(libraryItem, chapters, trackTotal, mimeType = null) {
|
|
||||||
const bookMetadata = libraryItem.media.metadata
|
|
||||||
const coverPath = libraryItem.media.coverPath
|
|
||||||
|
|
||||||
const isMp4 = mimeType === 'audio/mp4'
|
|
||||||
const isMp3 = mimeType === 'audio/mpeg'
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if (!isMp3) {
|
|
||||||
metadataObject.additionalFields['----:com.pilabor.tone:SERIES'] = bookMetadata.firstSeriesName
|
|
||||||
}
|
|
||||||
metadataObject['movementName'] = bookMetadata.firstSeriesName
|
|
||||||
}
|
|
||||||
if (bookMetadata.firstSeriesSequence) {
|
|
||||||
// Non-mp3
|
|
||||||
if (!isMp3) {
|
|
||||||
metadataObject.additionalFields['----:com.pilabor.tone:PART'] = bookMetadata.firstSeriesSequence
|
|
||||||
}
|
|
||||||
// MP3 Files with non-integer sequence
|
|
||||||
const isNonIntegerSequence = String(bookMetadata.firstSeriesSequence).includes('.') || isNaN(bookMetadata.firstSeriesSequence)
|
|
||||||
if (isMp3 && isNonIntegerSequence) {
|
|
||||||
metadataObject.additionalFields['PART'] = bookMetadata.firstSeriesSequence
|
|
||||||
}
|
|
||||||
if (!isNonIntegerSequence) {
|
|
||||||
metadataObject['movement'] = bookMetadata.firstSeriesSequence
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bookMetadata.genres.length) {
|
|
||||||
metadataObject['genre'] = bookMetadata.genres.join('/')
|
|
||||||
}
|
|
||||||
if (bookMetadata.publisher) {
|
|
||||||
metadataObject['publisher'] = bookMetadata.publisher
|
|
||||||
}
|
|
||||||
if (bookMetadata.asin) {
|
|
||||||
if (!isMp3) {
|
|
||||||
metadataObject.additionalFields['----:com.pilabor.tone:AUDIBLE_ASIN'] = bookMetadata.asin
|
|
||||||
}
|
|
||||||
if (!isMp4) {
|
|
||||||
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 metadataObject
|
|
||||||
}
|
|
||||||
module.exports.getToneMetadataObject = getToneMetadataObject
|
|
||||||
|
|
||||||
module.exports.writeToneMetadataJsonFile = (libraryItem, chapters, filePath, trackTotal, mimeType) => {
|
|
||||||
const metadataObject = getToneMetadataObject(libraryItem, chapters, trackTotal, mimeType)
|
|
||||||
return fs.writeFile(filePath, JSON.stringify({ meta: metadataObject }, null, 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.tagAudioFile = (filePath, payload) => {
|
|
||||||
if (process.env.TONE_PATH) {
|
|
||||||
tone.TONE_PATH = process.env.TONE_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
return tone.tag(filePath, payload).then((data) => {
|
|
||||||
return true
|
|
||||||
}).catch((error) => {
|
|
||||||
Logger.error(`[toneHelpers] tagAudioFile: Failed for "${filePath}"`, error)
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function parsePublishedYear(publishedYear) {
|
|
||||||
if (isNaN(publishedYear) || !publishedYear || Number(publishedYear) <= 0) return null
|
|
||||||
return `01/01/${publishedYear}`
|
|
||||||
}
|
|
@ -1,173 +0,0 @@
|
|||||||
const tone = require('node-tone')
|
|
||||||
const MediaProbeData = require('../scanner/MediaProbeData')
|
|
||||||
const Logger = require('../Logger')
|
|
||||||
|
|
||||||
/*
|
|
||||||
Sample dump from tone
|
|
||||||
{
|
|
||||||
"audio": {
|
|
||||||
"bitrate": 17,
|
|
||||||
"format": "MPEG-4 Part 14",
|
|
||||||
"formatShort": "MPEG-4",
|
|
||||||
"sampleRate": 44100.0,
|
|
||||||
"duration": 209284.0,
|
|
||||||
"channels": {
|
|
||||||
"count": 2,
|
|
||||||
"description": "Stereo (2/0.0)"
|
|
||||||
},
|
|
||||||
"frames": {
|
|
||||||
"offset": 42168,
|
|
||||||
"length": 446932
|
|
||||||
"metaFormat": [
|
|
||||||
"mp4"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"meta": {
|
|
||||||
"album": "node-tone",
|
|
||||||
"albumArtist": "advplyr",
|
|
||||||
"artist": "advplyr",
|
|
||||||
"composer": "Composer 5",
|
|
||||||
"comment": "testing out tone metadata",
|
|
||||||
"encodingTool": "audiobookshelf",
|
|
||||||
"genre": "abs",
|
|
||||||
"itunesCompilation": "no",
|
|
||||||
"itunesMediaType": "audiobook",
|
|
||||||
"itunesPlayGap": "noGap",
|
|
||||||
"narrator": "Narrator 5",
|
|
||||||
"recordingDate": "2022-09-10T00:00:00",
|
|
||||||
"title": "Test 5",
|
|
||||||
"trackNumber": 5,
|
|
||||||
"chapters": [
|
|
||||||
{
|
|
||||||
"start": 0,
|
|
||||||
"length": 500,
|
|
||||||
"title": "chapter 1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"start": 500,
|
|
||||||
"length": 500,
|
|
||||||
"title": "chapter 2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"start": 1000,
|
|
||||||
"length": 208284,
|
|
||||||
"title": "chapter 3"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"embeddedPictures": [
|
|
||||||
{
|
|
||||||
"code": 14,
|
|
||||||
"mimetype": "image/png",
|
|
||||||
"data": "..."
|
|
||||||
},
|
|
||||||
"additionalFields": {
|
|
||||||
"test": "Test 5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"file": {
|
|
||||||
"size": 530793,
|
|
||||||
"created": "2022-09-10T13:32:51.1942586-05:00",
|
|
||||||
"modified": "2022-09-10T14:09:19.366071-05:00",
|
|
||||||
"accessed": "2022-09-11T13:00:56.5097533-05:00",
|
|
||||||
"path": "C:\\Users\\Coop\\Documents\\NodeProjects\\node-tone\\samples",
|
|
||||||
"name": "m4b.m4b"
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
function bitrateKilobitToBit(bitrate) {
|
|
||||||
if (isNaN(bitrate) || !bitrate) return 0
|
|
||||||
return Number(bitrate) * 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
function msToSeconds(ms) {
|
|
||||||
if (isNaN(ms) || !ms) return 0
|
|
||||||
return Number(ms) / 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseProbeDump(dumpPayload) {
|
|
||||||
const audioMetadata = dumpPayload.audio
|
|
||||||
const audioChannels = audioMetadata.channels || {}
|
|
||||||
const audio_stream = {
|
|
||||||
bit_rate: bitrateKilobitToBit(audioMetadata.bitrate), // tone uses Kbps but ffprobe uses bps so convert to bits
|
|
||||||
codec: null,
|
|
||||||
time_base: null,
|
|
||||||
language: null,
|
|
||||||
channel_layout: audioChannels.description || null,
|
|
||||||
channels: audioChannels.count || null,
|
|
||||||
sample_rate: audioMetadata.sampleRate || null
|
|
||||||
}
|
|
||||||
|
|
||||||
let chapterIndex = 0
|
|
||||||
const chapters = (dumpPayload.meta.chapters || []).map(chap => {
|
|
||||||
return {
|
|
||||||
id: chapterIndex++,
|
|
||||||
start: msToSeconds(chap.start),
|
|
||||||
end: msToSeconds(chap.start + chap.length),
|
|
||||||
title: chap.title || ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var video_stream = null
|
|
||||||
if (dumpPayload.meta.embeddedPictures && dumpPayload.meta.embeddedPictures.length) {
|
|
||||||
const mimetype = dumpPayload.meta.embeddedPictures[0].mimetype
|
|
||||||
video_stream = {
|
|
||||||
codec: mimetype === 'image/png' ? 'png' : 'jpeg'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tags = { ...dumpPayload.meta }
|
|
||||||
delete tags.chapters
|
|
||||||
delete tags.embeddedPictures
|
|
||||||
|
|
||||||
const fileMetadata = dumpPayload.file
|
|
||||||
var sizeBytes = !isNaN(fileMetadata.size) ? Number(fileMetadata.size) : null
|
|
||||||
var sizeMb = sizeBytes !== null ? Number((sizeBytes / (1024 * 1024)).toFixed(2)) : null
|
|
||||||
return {
|
|
||||||
format: audioMetadata.format || 'Unknown',
|
|
||||||
duration: msToSeconds(audioMetadata.duration),
|
|
||||||
size: sizeBytes,
|
|
||||||
sizeMb,
|
|
||||||
bit_rate: audio_stream.bit_rate,
|
|
||||||
audio_stream,
|
|
||||||
video_stream,
|
|
||||||
chapters,
|
|
||||||
tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.probe = (filepath, verbose = false) => {
|
|
||||||
if (process.env.TONE_PATH) {
|
|
||||||
tone.TONE_PATH = process.env.TONE_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
return tone.dump(filepath).then((dumpPayload) => {
|
|
||||||
if (verbose) {
|
|
||||||
Logger.debug(`[toneProber] dump for file "${filepath}"`, dumpPayload)
|
|
||||||
}
|
|
||||||
const rawProbeData = parseProbeDump(dumpPayload)
|
|
||||||
const probeData = new MediaProbeData()
|
|
||||||
probeData.setDataFromTone(rawProbeData)
|
|
||||||
return probeData
|
|
||||||
}).catch((error) => {
|
|
||||||
Logger.error(`[toneProber] Failed to probe file at path "${filepath}"`, error)
|
|
||||||
return {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.rawProbe = (filepath) => {
|
|
||||||
if (process.env.TONE_PATH) {
|
|
||||||
tone.TONE_PATH = process.env.TONE_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
return tone.dump(filepath).then((dumpPayload) => {
|
|
||||||
return dumpPayload
|
|
||||||
}).catch((error) => {
|
|
||||||
Logger.error(`[toneProber] Failed to probe file at path "${filepath}"`, error)
|
|
||||||
return {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user