diff --git a/client/components/modals/AudioFileDataModal.vue b/client/components/modals/AudioFileDataModal.vue index fae88056..a253cee5 100644 --- a/client/components/modals/AudioFileDataModal.vue +++ b/client/components/modals/AudioFileDataModal.vue @@ -1,84 +1,99 @@ diff --git a/client/components/tables/TracksTable.vue b/client/components/tables/TracksTable.vue index 22944453..2554fff1 100644 --- a/client/components/tables/TracksTable.vue +++ b/client/components/tables/TracksTable.vue @@ -33,7 +33,7 @@ - + diff --git a/client/components/ui/TextareaWithLabel.vue b/client/components/ui/TextareaWithLabel.vue index 96f98c92..f2e237e0 100644 --- a/client/components/ui/TextareaWithLabel.vue +++ b/client/components/ui/TextareaWithLabel.vue @@ -1,7 +1,7 @@ @@ -11,6 +11,7 @@ export default { value: [String, Number], label: String, disabled: Boolean, + readonly: Boolean, rows: { type: Number, default: 2 diff --git a/client/plugins/init.client.js b/client/plugins/init.client.js index ea6db96a..711c526a 100644 --- a/client/plugins/init.client.js +++ b/client/plugins/init.client.js @@ -24,20 +24,20 @@ Vue.prototype.$formatJsDate = (jsdate, fnsFormat = 'MM/dd/yyyy HH:mm') => { return format(jsdate, fnsFormat) } Vue.prototype.$formatTime = (unixms, fnsFormat = 'HH:mm') => { - if (!unixms) return '' - return format(unixms, fnsFormat) + if (!unixms) return '' + return format(unixms, fnsFormat) } Vue.prototype.$formatJsTime = (jsdate, fnsFormat = 'HH:mm') => { - if (!jsdate || !isDate(jsdate)) return '' - return format(jsdate, fnsFormat) + if (!jsdate || !isDate(jsdate)) return '' + return format(jsdate, fnsFormat) } Vue.prototype.$formatDatetime = (unixms, fnsDateFormart = 'MM/dd/yyyy', fnsTimeFormat = 'HH:mm') => { - if (!unixms) return '' - return format(unixms, `${fnsDateFormart} ${fnsTimeFormat}`) + if (!unixms) return '' + return format(unixms, `${fnsDateFormart} ${fnsTimeFormat}`) } Vue.prototype.$formatJsDatetime = (jsdate, fnsDateFormart = 'MM/dd/yyyy', fnsTimeFormat = 'HH:mm') => { - if (!jsdate || !isDate(jsdate)) return '' - return format(jsdate, `${fnsDateFormart} ${fnsTimeFormat}`) + if (!jsdate || !isDate(jsdate)) return '' + return format(jsdate, `${fnsDateFormart} ${fnsTimeFormat}`) } Vue.prototype.$addDaysToToday = (daysToAdd) => { var date = addDays(new Date(), daysToAdd) @@ -132,8 +132,10 @@ Vue.prototype.$copyToClipboard = (str, ctx) => { if (navigator.clipboard) { navigator.clipboard.writeText(str).then(() => { if (ctx) ctx.$toast.success('Copied to clipboard') + resolve(true) }, (err) => { console.error('Clipboard copy failed', str, err) + resolve(false) }) } else { const el = document.createElement('textarea') @@ -147,6 +149,7 @@ Vue.prototype.$copyToClipboard = (str, ctx) => { document.body.removeChild(el) if (ctx) ctx.$toast.success('Copied to clipboard') + resolve(true) } }) } diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 5ccbceab..085ff03f 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -472,7 +472,7 @@ class LibraryItemController { getToneMetadataObject(req, res) { if (!req.user.isAdminOrUp) { - Logger.error(`[LibraryItemController] Non-root user attempted to get tone metadata object`, req.user) + Logger.error(`[LibraryItemController] Non-admin user attempted to get tone metadata object`, req.user) return res.sendStatus(403) } @@ -514,20 +514,31 @@ class LibraryItemController { }) } - async toneScan(req, res) { - if (!req.libraryItem.media.audioFiles.length) { - return res.sendStatus(404) + /** + * GET api/items/:id/ffprobe/:fileid + * FFProbe JSON result from audio file + * + * @param {express.Request} req + * @param {express.Response} res + */ + async getFFprobeData(req, res) { + if (!req.user.isAdminOrUp) { + Logger.error(`[LibraryItemController] Non-admin user attempted to get ffprobe data`, req.user) + return res.sendStatus(403) + } + if (req.libraryFile.fileType !== 'audio') { + Logger.error(`[LibraryItemController] Invalid filetype "${req.libraryFile.fileType}" for fileid "${req.params.fileid}". Expected audio file`) + return res.sendStatus(400) } - const audioFileIndex = isNullOrNaN(req.params.index) ? 1 : Number(req.params.index) - const audioFile = req.libraryItem.media.audioFiles.find(af => af.index === audioFileIndex) + const audioFile = req.libraryItem.media.findFileWithInode(req.params.fileid) if (!audioFile) { - Logger.error(`[LibraryItemController] toneScan: Audio file not found with index ${audioFileIndex}`) + Logger.error(`[LibraryItemController] Audio file not found with inode value ${req.params.fileid}`) return res.sendStatus(404) } - const toneData = await this.scanner.probeAudioFileWithTone(audioFile) - res.json(toneData) + const ffprobeData = await this.scanner.probeAudioFile(audioFile) + res.json(ffprobeData) } /** diff --git a/server/objects/mediaTypes/Book.js b/server/objects/mediaTypes/Book.js index aea45e60..f94a240c 100644 --- a/server/objects/mediaTypes/Book.js +++ b/server/objects/mediaTypes/Book.js @@ -198,6 +198,7 @@ class Book { this.coverPath = coverPath return true } + removeFileWithInode(inode) { if (this.audioFiles.some(af => af.ino === inode)) { this.audioFiles = this.audioFiles.filter(af => af.ino !== inode) @@ -210,8 +211,13 @@ class Book { return false } + /** + * Get audio file or ebook file from inode + * @param {string} inode + * @returns {(AudioFile|EBookFile|null)} + */ findFileWithInode(inode) { - var audioFile = this.audioFiles.find(af => af.ino === inode) + const audioFile = this.audioFiles.find(af => af.ino === inode) if (audioFile) return audioFile if (this.ebookFile && this.ebookFile.ino === inode) return this.ebookFile return null diff --git a/server/objects/settings/ServerSettings.js b/server/objects/settings/ServerSettings.js index e1481ed5..a303a790 100644 --- a/server/objects/settings/ServerSettings.js +++ b/server/objects/settings/ServerSettings.js @@ -15,7 +15,6 @@ class ServerSettings { this.scannerPreferMatchedMetadata = false this.scannerDisableWatcher = false this.scannerPreferOverdriveMediaMarker = false - this.scannerUseTone = false // Metadata - choose to store inside users library item folder this.storeCoverWithItem = false @@ -72,7 +71,6 @@ class ServerSettings { this.scannerPreferMatchedMetadata = !!settings.scannerPreferMatchedMetadata this.scannerDisableWatcher = !!settings.scannerDisableWatcher this.scannerPreferOverdriveMediaMarker = !!settings.scannerPreferOverdriveMediaMarker - this.scannerUseTone = !!settings.scannerUseTone this.storeCoverWithItem = !!settings.storeCoverWithItem this.storeMetadataWithItem = !!settings.storeMetadataWithItem @@ -139,7 +137,6 @@ class ServerSettings { scannerPreferMatchedMetadata: this.scannerPreferMatchedMetadata, scannerDisableWatcher: this.scannerDisableWatcher, scannerPreferOverdriveMediaMarker: this.scannerPreferOverdriveMediaMarker, - scannerUseTone: this.scannerUseTone, storeCoverWithItem: this.storeCoverWithItem, storeMetadataWithItem: this.storeMetadataWithItem, metadataFileFormat: this.metadataFileFormat, diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index f41f62bd..d23f6400 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -121,7 +121,7 @@ class ApiRouter { 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.post('/items/:id/chapters', LibraryItemController.middleware.bind(this), LibraryItemController.updateMediaChapters.bind(this)) - this.router.post('/items/:id/tone-scan/:index?', LibraryItemController.middleware.bind(this), LibraryItemController.toneScan.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.delete('/items/:id/file/:fileid', LibraryItemController.middleware.bind(this), LibraryItemController.deleteLibraryFile.bind(this)) this.router.get('/items/:id/file/:fileid/download', LibraryItemController.middleware.bind(this), LibraryItemController.downloadLibraryFile.bind(this)) diff --git a/server/scanner/MediaFileScanner.js b/server/scanner/MediaFileScanner.js index b0c00b20..d4d041c6 100644 --- a/server/scanner/MediaFileScanner.js +++ b/server/scanner/MediaFileScanner.js @@ -59,14 +59,7 @@ class MediaFileScanner { async scan(mediaType, libraryFile, mediaMetadataFromScan, verbose = false) { const probeStart = Date.now() - let probeData = null - // TODO: Temp not using tone for probing until more testing can be done - // if (global.ServerSettings.scannerUseTone) { - // Logger.debug(`[MediaFileScanner] using tone to probe audio file "${libraryFile.metadata.path}"`) - // probeData = await toneProber.probe(libraryFile.metadata.path, true) - // } else { - probeData = await prober.probe(libraryFile.metadata.path, verbose) - // } + const probeData = await prober.probe(libraryFile.metadata.path, verbose) if (probeData.error) { Logger.error(`[MediaFileScanner] ${probeData.error} : "${libraryFile.metadata.path}"`) @@ -332,9 +325,9 @@ class MediaFileScanner { return hasUpdated } - probeAudioFileWithTone(audioFile) { - Logger.debug(`[MediaFileScanner] using tone to probe audio file "${audioFile.metadata.path}"`) - return toneProber.rawProbe(audioFile.metadata.path) + probeAudioFile(audioFile) { + Logger.debug(`[MediaFileScanner] Running ffprobe for audio file at "${audioFile.metadata.path}"`) + return prober.rawProbe(audioFile.metadata.path) } } module.exports = new MediaFileScanner() \ No newline at end of file diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index b1846018..28874b8c 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -1034,8 +1034,8 @@ class Scanner { SocketAuthority.emitter('scan_complete', libraryScan.getScanEmitData) } - probeAudioFileWithTone(audioFile) { - return MediaFileScanner.probeAudioFileWithTone(audioFile) + probeAudioFile(audioFile) { + return MediaFileScanner.probeAudioFile(audioFile) } } module.exports = Scanner diff --git a/server/utils/prober.js b/server/utils/prober.js index 29db840d..8c818d53 100644 --- a/server/utils/prober.js +++ b/server/utils/prober.js @@ -309,3 +309,23 @@ function probe(filepath, verbose = false) { }) } module.exports.probe = probe + +/** + * Ffprobe for audio file path + * + * @param {string} filepath + * @returns {Object} ffprobe json output + */ +function rawProbe(filepath) { + if (process.env.FFPROBE_PATH) { + ffprobe.FFPROBE_PATH = process.env.FFPROBE_PATH + } + + return ffprobe(filepath) + .catch((err) => { + return { + error: err + } + }) +} +module.exports.rawProbe = rawProbe \ No newline at end of file