diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue
index f83cbae0..e2119bf8 100644
--- a/client/components/app/BookShelfCategorized.vue
+++ b/client/components/app/BookShelfCategorized.vue
@@ -85,7 +85,7 @@ export default {
},
async fetchCategories() {
var categories = await this.$axios
- .$get(`/api/libraries/${this.currentLibraryId}/categories`)
+ .$get(`/api/libraries/${this.currentLibraryId}/categories?minified=1`)
.then((data) => {
return data
})
diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue
index 34d6337b..57d1a46e 100644
--- a/client/components/app/LazyBookshelf.vue
+++ b/client/components/app/LazyBookshelf.vue
@@ -54,7 +54,7 @@ export default {
pageLoadQueue: [],
isFetchingEntities: false,
scrollTimeout: null,
- booksPerFetch: 250,
+ booksPerFetch: 100,
totalShelves: 0,
bookshelfMarginLeft: 0,
isSelectionMode: false,
@@ -220,7 +220,7 @@ export default {
var entityPath = this.entityName === 'books' ? `books/all` : this.entityName
if (this.entityName === 'series-books') entityPath = `series/${this.seriesId}`
var sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : ''
- var fullQueryString = this.entityName === 'series-books' ? '' : `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}`
+ var fullQueryString = this.entityName === 'series-books' ? '' : `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1`
var payload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/${entityPath}${fullQueryString}`).catch((error) => {
console.error('failed to fetch books', error)
return null
@@ -438,7 +438,7 @@ export default {
var entitiesPerShelfBefore = this.entitiesPerShelf
var { clientHeight, clientWidth } = bookshelf
- console.log('Init bookshelf width', clientWidth, 'window width', window.innerWidth)
+ // console.log('Init bookshelf width', clientWidth, 'window width', window.innerWidth)
this.mountWindowWidth = window.innerWidth
this.bookshelfHeight = clientHeight
this.bookshelfWidth = clientWidth
diff --git a/client/components/app/StreamContainer.vue b/client/components/app/StreamContainer.vue
index 1e4850df..0c5afd09 100644
--- a/client/components/app/StreamContainer.vue
+++ b/client/components/app/StreamContainer.vue
@@ -182,6 +182,8 @@ export default {
},
streamOpen(stream) {
this.stream = stream
+ this.$store.commit('updateStreamAudiobook', stream.audiobook)
+
if (this.$refs.audioPlayer) {
console.log('[StreamContainer] streamOpen', stream)
this.openStream()
diff --git a/client/components/modals/Modal.vue b/client/components/modals/Modal.vue
index 211da673..5ba357c6 100644
--- a/client/components/modals/Modal.vue
+++ b/client/components/modals/Modal.vue
@@ -6,7 +6,7 @@
close
-
+
@@ -49,7 +49,8 @@ export default {
data() {
return {
el: null,
- content: null
+ content: null,
+ preventClickoutside: false
}
},
watch: {
@@ -82,10 +83,20 @@ export default {
}
},
methods: {
+ mousedownModal() {
+ this.preventClickoutside = true
+ },
+ mouseupModal() {
+ this.preventClickoutside = false
+ },
clickClose() {
this.show = false
},
clickBg(ev) {
+ if (this.preventClickoutside) {
+ this.preventClickoutside = false
+ return
+ }
if (this.processing && this.persistent) return
if (ev.srcElement.classList.contains('modal-bg')) {
this.show = false
diff --git a/client/package.json b/client/package.json
index a75440dc..c7e9d0eb 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf-client",
- "version": "1.6.46",
+ "version": "1.6.47",
"description": "Audiobook manager and player",
"main": "index.js",
"scripts": {
diff --git a/client/pages/audiobook/_id/edit.vue b/client/pages/audiobook/_id/edit.vue
index 7d4ae2fb..27c378ac 100644
--- a/client/pages/audiobook/_id/edit.vue
+++ b/client/pages/audiobook/_id/edit.vue
@@ -73,30 +73,6 @@
-
-
-
Check Track Numbers
-
-
-
- Filename |
- Index |
- # From Metadata |
- # From Filename |
- # From Probe |
- Raw Tags |
-
-
- {{ trackData.filename }} |
- {{ trackData.currentTrackNum }} |
- {{ trackData.trackNumFromMeta }} |
- {{ trackData.trackNumFromFilename }} |
- {{ trackData.scanDataTrackNum }} |
- {{ JSON.stringify(trackData.rawTags || '') }} |
-
-
-
-
@@ -137,8 +113,6 @@ export default {
ghostClass: 'ghost'
},
saving: false,
- checkingTrackNumbers: false,
- trackNumData: [],
currentSort: 'current'
}
},
@@ -252,19 +226,6 @@ export default {
})
this.currentSort = 'filename'
},
- checkTrackNumbers() {
- this.checkingTrackNumbers = true
- this.$axios
- .$get(`/api/scantracks/${this.audiobookId}`)
- .then((res) => {
- this.trackNumData = res
- this.checkingTrackNumbers = false
- })
- .catch((error) => {
- console.error('Failed', error)
- this.checkingTrackNumbers = false
- })
- },
includeToggled(audio) {
var new_index = 0
if (audio.include) {
diff --git a/client/store/index.js b/client/store/index.js
index 35622679..443aabad 100644
--- a/client/store/index.js
+++ b/client/store/index.js
@@ -110,6 +110,9 @@ export const mutations = {
state.playOnLoad = true
state.streamAudiobook = audiobook
},
+ updateStreamAudiobook(state, audiobook) { // Initial stream audiobook is minified, on open audiobook is updated to full
+ state.streamAudiobook = audiobook
+ },
setStream(state, stream) {
state.playOnLoad = false
state.streamAudiobook = stream ? stream.audiobook : null
diff --git a/package.json b/package.json
index 943c0d31..86dbccd1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf",
- "version": "1.6.46",
+ "version": "1.6.47",
"description": "Self-hosted audiobook server for managing and playing audiobooks",
"main": "index.js",
"scripts": {
diff --git a/server/ApiController.js b/server/ApiController.js
index c767b485..cfd99cac 100644
--- a/server/ApiController.js
+++ b/server/ApiController.js
@@ -5,7 +5,6 @@ const date = require('date-and-time')
const Logger = require('./Logger')
const { isObject } = require('./utils/index')
-const audioFileScanner = require('./utils/audioFileScanner')
const BookController = require('./controllers/BookController')
const LibraryController = require('./controllers/LibraryController')
@@ -18,9 +17,8 @@ const BookFinder = require('./BookFinder')
const AuthorFinder = require('./AuthorFinder')
class ApiController {
- constructor(MetadataPath, db, scanner, auth, streamManager, rssFeeds, downloadManager, coverController, backupManager, watcher, cacheManager, emitter, clientEmitter) {
+ constructor(MetadataPath, db, auth, streamManager, rssFeeds, downloadManager, coverController, backupManager, watcher, cacheManager, emitter, clientEmitter) {
this.db = db
- this.scanner = scanner
this.auth = auth
this.streamManager = streamManager
this.rssFeeds = rssFeeds
@@ -167,8 +165,6 @@ class ApiController {
this.router.get('/filesystem', this.getFileSystemPaths.bind(this))
- this.router.get('/scantracks/:id', this.scanAudioTrackNums.bind(this))
-
this.router.post('/syncUserAudiobookData', this.syncUserAudiobookData.bind(this))
this.router.post('/purgecache', this.purgeCache.bind(this))
@@ -362,19 +358,6 @@ class ApiController {
res.json(dirs)
}
- async scanAudioTrackNums(req, res) {
- if (!req.user || !req.user.isRoot) {
- return res.sendStatus(403)
- }
- var audiobook = this.db.audiobooks.find(ab => ab.id === req.params.id)
- if (!audiobook) {
- return res.status(404).send('Audiobook not found')
- }
-
- var scandata = await audioFileScanner.scanTrackNumbers(audiobook)
- res.json(scandata)
- }
-
async syncUserAudiobookData(req, res) {
if (!req.body.data) {
return res.status(403).send('Invalid local user audiobook data')
diff --git a/server/HlsController.js b/server/HlsController.js
index e29c4cbc..ed44c84f 100644
--- a/server/HlsController.js
+++ b/server/HlsController.js
@@ -4,9 +4,8 @@ const fs = require('fs-extra')
const Logger = require('./Logger')
class HlsController {
- constructor(db, scanner, auth, streamManager, emitter, StreamsPath) {
+ constructor(db, auth, streamManager, emitter, StreamsPath) {
this.db = db
- this.scanner = scanner
this.auth = auth
this.streamManager = streamManager
this.emitter = emitter
diff --git a/server/Scanner.js b/server/Scanner.js
deleted file mode 100644
index edd80114..00000000
--- a/server/Scanner.js
+++ /dev/null
@@ -1,768 +0,0 @@
-const fs = require('fs-extra')
-const Path = require('path')
-
-// Utils
-const Logger = require('./Logger')
-const { version } = require('../package.json')
-const audioFileScanner = require('./utils/audioFileScanner')
-const { groupFilesIntoAudiobookPaths, getAudiobookFileData, scanRootDir } = require('./utils/scandir')
-const { comparePaths, getIno, getId, secondsToTimestamp } = require('./utils/index')
-const { ScanResult, CoverDestination } = require('./utils/constants')
-
-const BookFinder = require('./BookFinder')
-const Audiobook = require('./objects/Audiobook')
-
-class Scanner {
- constructor(AUDIOBOOK_PATH, METADATA_PATH, db, coverController, emitter) {
- this.AudiobookPath = AUDIOBOOK_PATH
- this.MetadataPath = METADATA_PATH
- this.BookMetadataPath = Path.posix.join(this.MetadataPath.replace(/\\/g, '/'), 'books')
-
- this.db = db
- this.coverController = coverController
- this.emitter = emitter
-
- this.cancelScan = false
- this.cancelLibraryScan = {}
- this.librariesScanning = []
-
- this.bookFinder = new BookFinder()
- }
-
- get audiobooks() {
- return this.db.audiobooks
- }
-
- getCoverDirectory(audiobook) {
- if (this.db.serverSettings.coverDestination === CoverDestination.AUDIOBOOK) {
- return {
- fullPath: audiobook.fullPath,
- relPath: '/s/book/' + audiobook.id
- }
- } else {
- return {
- fullPath: Path.posix.join(this.BookMetadataPath, audiobook.id),
- relPath: Path.posix.join('/metadata', 'books', audiobook.id)
- }
- }
- }
-
- async setAudioFileInos(audiobookDataAudioFiles, audiobookAudioFiles) {
- for (let i = 0; i < audiobookDataAudioFiles.length; i++) {
- var abdFile = audiobookDataAudioFiles[i]
- var matchingFile = audiobookAudioFiles.find(af => comparePaths(af.path, abdFile.path))
- if (matchingFile) {
- if (!matchingFile.ino) {
- matchingFile.ino = await getIno(matchingFile.fullPath)
- }
- abdFile.ino = matchingFile.ino
- } else {
- abdFile.ino = await getIno(abdFile.fullPath)
- if (!abdFile.ino) {
- Logger.error('[Scanner] Invalid abdFile ino - ignoring abd audio file', abdFile.path)
- }
- }
- }
- return audiobookDataAudioFiles.filter(abdFile => !!abdFile.ino)
- }
-
- // Only updates audio files with matching paths
- syncAudiobookInodeValues(audiobook, { audioFiles, otherFiles }) {
- var filesUpdated = 0
-
- // Sync audio files & audio tracks with updated inodes
- audiobook._audioFiles.forEach((audioFile) => {
- var matchingAudioFile = audioFiles.find(af => af.ino !== audioFile.ino && af.path === audioFile.path)
- if (matchingAudioFile) {
- // Audio Track should always have the same ino as the equivalent audio file (not all audio files have a track)
- var audioTrack = audiobook.tracks.find(t => t.ino === audioFile.ino)
- if (audioTrack) {
- Logger.debug(`[Scanner] Found audio file & track with mismatched inode "${audioFile.filename}" - updating it`)
- audioTrack.ino = matchingAudioFile.ino
- filesUpdated++
- } else {
- Logger.debug(`[Scanner] Found audio file with mismatched inode "${audioFile.filename}" - updating it`)
- }
-
- audioFile.ino = matchingAudioFile.ino
- filesUpdated++
- }
- })
-
- // Sync other files with updated inodes
- audiobook._otherFiles.forEach((otherFile) => {
- var matchingOtherFile = otherFiles.find(of => of.ino !== otherFile.ino && of.path === otherFile.path)
- if (matchingOtherFile) {
- Logger.debug(`[Scanner] Found other file with mismatched inode "${otherFile.filename}" - updating it`)
- otherFile.ino = matchingOtherFile.ino
- filesUpdated++
- }
- })
-
- return filesUpdated
- }
-
- async searchForCover(audiobook) {
- var options = {
- titleDistance: 2,
- authorDistance: 2
- }
- var results = await this.bookFinder.findCovers('openlibrary', audiobook.title, audiobook.authorFL, options)
- if (results.length) {
- Logger.debug(`[Scanner] Found best cover for "${audiobook.title}"`)
-
- // If the first cover result fails, attempt to download the second
- for (let i = 0; i < results.length && i < 2; i++) {
-
- // Downloads and updates the book cover
- var result = await this.coverController.downloadCoverFromUrl(audiobook, results[i])
-
- if (result.error) {
- Logger.error(`[Scanner] Failed to download cover from url "${results[i]}" | Attempt ${i + 1}`, result.error)
- } else {
- return true
- }
- }
- }
- return false
- }
-
- async scanExistingAudiobook(existingAudiobook, audiobookData, hasUpdatedIno, hasUpdatedLibraryOrFolder, forceAudioFileScan) {
- // Always sync files and inode values
- var filesInodeUpdated = this.syncAudiobookInodeValues(existingAudiobook, audiobookData)
- if (hasUpdatedIno || filesInodeUpdated > 0) {
- Logger.info(`[Scanner] Updating inode value for "${existingAudiobook.title}" - ${filesInodeUpdated} files updated`)
- hasUpdatedIno = true
- }
-
- // TEMP: Check if is older audiobook and needs force rescan
- if (!forceAudioFileScan && (!existingAudiobook.scanVersion || existingAudiobook.checkHasOldCoverPath())) {
- Logger.info(`[Scanner] Force rescan for "${existingAudiobook.title}" | Last scan v${existingAudiobook.scanVersion} | Old Cover Path ${!!existingAudiobook.checkHasOldCoverPath()}`)
- forceAudioFileScan = true
- }
-
- // inode is required
- audiobookData.audioFiles = audiobookData.audioFiles.filter(af => af.ino)
-
- // No valid ebook and audio files found, mark as incomplete
- var ebookFiles = audiobookData.otherFiles.filter(f => f.filetype === 'ebook')
- if (!audiobookData.audioFiles.length && !ebookFiles.length) {
- Logger.error(`[Scanner] "${existingAudiobook.title}" no valid book files found - marking as incomplete`)
- existingAudiobook.setLastScan(version)
- existingAudiobook.isInvalid = true
- await this.db.updateAudiobook(existingAudiobook)
- this.emitter('audiobook_updated', existingAudiobook.toJSONMinified())
- return ScanResult.UPDATED
- } else if (existingAudiobook.isInvalid) { // Was incomplete but now is not
- Logger.info(`[Scanner] "${existingAudiobook.title}" was incomplete but now has book files`)
- existingAudiobook.isInvalid = false
- }
-
- // Check for audio files that were removed
- var abdAudioFileInos = audiobookData.audioFiles.map(af => af.ino)
- var removedAudioFiles = existingAudiobook.audioFiles.filter(file => !abdAudioFileInos.includes(file.ino))
- if (removedAudioFiles.length) {
- Logger.info(`[Scanner] ${removedAudioFiles.length} audio files removed for audiobook "${existingAudiobook.title}"`)
- removedAudioFiles.forEach((af) => existingAudiobook.removeAudioFile(af))
- }
-
- // Check for mismatched audio tracks - tracks with no matching audio file
- var removedAudioTracks = existingAudiobook.tracks.filter(track => !abdAudioFileInos.includes(track.ino))
- if (removedAudioTracks.length) {
- Logger.error(`[Scanner] ${removedAudioTracks.length} tracks removed no matching audio file for audiobook "${existingAudiobook.title}"`)
- removedAudioTracks.forEach((at) => existingAudiobook.removeAudioTrack(at))
- }
-
- // Check for new audio files and sync existing audio files
- var newAudioFiles = []
- var hasUpdatedAudioFiles = false
- audiobookData.audioFiles.forEach((file) => {
- var existingAudioFile = existingAudiobook.getAudioFileByIno(file.ino)
- if (existingAudioFile) { // Audio file exists, sync path (path may have been renamed)
- if (existingAudiobook.syncAudioFile(existingAudioFile, file)) {
- hasUpdatedAudioFiles = true
- }
- } else {
- // New audio file, triple check for matching file path
- var audioFileWithMatchingPath = existingAudiobook.getAudioFileByPath(file.fullPath)
- if (audioFileWithMatchingPath) {
- Logger.warn(`[Scanner] Audio file with path already exists with different inode, New: "${file.filename}" (${file.ino}) | Existing: ${audioFileWithMatchingPath.filename} (${audioFileWithMatchingPath.ino})`)
- } else {
- newAudioFiles.push(file)
- }
- }
- })
-
-
- // Sync other files (all files that are not audio files) - Updates cover path
- var hasOtherFileUpdates = false
- var otherFilesUpdated = await existingAudiobook.syncOtherFiles(audiobookData.otherFiles, this.MetadataPath, false, forceAudioFileScan)
- if (otherFilesUpdated) {
- hasOtherFileUpdates = true
- }
-
- // Rescan audio file metadata
- if (forceAudioFileScan) {
- Logger.info(`[Scanner] Rescanning ${existingAudiobook.audioFiles.length} audio files for "${existingAudiobook.title}"`)
- var numAudioFilesUpdated = await audioFileScanner.rescanAudioFiles(existingAudiobook)
-
- // Set book details from metadata pulled from audio files
- var bookMetadataUpdated = existingAudiobook.setDetailsFromFileMetadata()
- if (bookMetadataUpdated) {
- Logger.debug(`[Scanner] Book Metadata Updated for "${existingAudiobook.title}"`)
- hasUpdatedAudioFiles = true
- }
-
- if (numAudioFilesUpdated > 0) {
- Logger.info(`[Scanner] Rescan complete, ${numAudioFilesUpdated} audio files were updated for "${existingAudiobook.title}"`)
- hasUpdatedAudioFiles = true
-
- // Use embedded cover art if audiobook has no cover
- if (existingAudiobook.hasEmbeddedCoverArt && !existingAudiobook.cover) {
- var outputCoverDirs = this.getCoverDirectory(existingAudiobook)
- var relativeDir = await existingAudiobook.saveEmbeddedCoverArt(outputCoverDirs.fullPath, outputCoverDirs.relPath)
- if (relativeDir) {
- Logger.debug(`[Scanner] Saved embedded cover art "${relativeDir}"`)
- }
- }
- } else {
- Logger.info(`[Scanner] Rescan complete, audio files were up to date for "${existingAudiobook.title}"`)
- }
- }
-
- // Scan and add new audio files found and set tracks
- if (newAudioFiles.length) {
- Logger.info(`[Scanner] ${newAudioFiles.length} new audio files were found for audiobook "${existingAudiobook.title}"`)
- await audioFileScanner.scanAudioFiles(existingAudiobook, newAudioFiles)
- }
-
- // After scanning audio files, some may no longer be valid
- // so make sure the directory still has valid book files
- if (!existingAudiobook.tracks.length && !ebookFiles.length) {
- Logger.error(`[Scanner] "${existingAudiobook.title}" no valid book files found after update - marking as incomplete`)
- existingAudiobook.setLastScan(version)
- existingAudiobook.isInvalid = true
- await this.db.updateAudiobook(existingAudiobook)
- this.emitter('audiobook_updated', existingAudiobook.toJSONMinified())
- return ScanResult.UPDATED
- }
-
- var hasUpdates = hasOtherFileUpdates || hasUpdatedIno || hasUpdatedLibraryOrFolder || removedAudioFiles.length || removedAudioTracks.length || newAudioFiles.length || hasUpdatedAudioFiles
-
- // Check that audio tracks are in sequential order with no gaps
- if (existingAudiobook.checkUpdateMissingTracks()) {
- Logger.info(`[Scanner] "${existingAudiobook.title}" missing parts updated`)
- hasUpdates = true
- }
-
- // Syncs path and fullPath
- if (existingAudiobook.syncPaths(audiobookData)) {
- hasUpdates = true
- }
-
- // If audiobook was missing before, it is now found
- if (existingAudiobook.isMissing) {
- existingAudiobook.isMissing = false
- hasUpdates = true
- Logger.info(`[Scanner] "${existingAudiobook.title}" was missing but now it is found`)
- }
-
- if (hasUpdates || version !== existingAudiobook.scanVersion) {
- existingAudiobook.setChapters()
- existingAudiobook.setLastScan(version)
- await this.db.updateAudiobook(existingAudiobook)
-
- Logger.info(`[Scanner] "${existingAudiobook.title}" was updated`)
- this.emitter('audiobook_updated', existingAudiobook.toJSONMinified())
- return ScanResult.UPDATED
- }
-
- return ScanResult.UPTODATE
- }
-
- async scanNewAudiobook(audiobookData) {
- var ebookFiles = audiobookData.otherFiles.map(f => f.filetype === 'ebook')
- if (!audiobookData.audioFiles.length && !ebookFiles.length) {
- Logger.error('[Scanner] No valid audio files and ebooks for Audiobook', audiobookData.path)
- return null
- }
-
- var audiobook = new Audiobook()
- audiobook.setData(audiobookData)
-
- // Scan audio files and set tracks, pulls metadata
- await audioFileScanner.scanAudioFiles(audiobook, audiobookData.audioFiles)
-
- if (!audiobook.tracks.length && !audiobook.ebooks.length) {
- Logger.warn('[Scanner] Invalid audiobook, no valid audio tracks and ebook files', audiobook.title)
- return null
- }
-
- // Look for desc.txt and reader.txt and update
- await audiobook.saveDataFromTextFiles(false)
-
- // Extract embedded cover art if cover is not already in directory
- if (audiobook.hasEmbeddedCoverArt && !audiobook.cover) {
- var outputCoverDirs = this.getCoverDirectory(audiobook)
- var relativeDir = await audiobook.saveEmbeddedCoverArt(outputCoverDirs.fullPath, outputCoverDirs.relPath)
- if (relativeDir) {
- Logger.debug(`[Scanner] Saved embedded cover art "${relativeDir}"`)
- }
- }
-
- // Set book details from metadata pulled from audio files
- audiobook.setDetailsFromFileMetadata()
-
- // Check for gaps in track numbers
- audiobook.checkUpdateMissingTracks()
-
- // Set chapters from audio files
- audiobook.setChapters()
-
- audiobook.setLastScan(version)
-
- Logger.info(`[Scanner] Audiobook "${audiobook.title}" Scanned (${audiobook.sizePretty}) [${audiobook.durationPretty}]`)
- await this.db.insertEntity('audiobook', audiobook)
- this.emitter('audiobook_added', audiobook.toJSONMinified())
- return audiobook
- }
-
- async scanAudiobookData(audiobookData, forceAudioFileScan = false) {
- var scannerFindCovers = this.db.serverSettings.scannerFindCovers
- var libraryId = audiobookData.libraryId
- var folderId = audiobookData.folderId
-
- var hasUpdatedLibraryOrFolder = false
-
- var existingAudiobook = this.audiobooks.find(ab => ab.ino === audiobookData.ino)
-
- // Make sure existing audiobook has the same library & folder id
- if (existingAudiobook && (existingAudiobook.libraryId !== libraryId || existingAudiobook.folderId !== folderId)) {
- var existingAudiobookLibrary = this.db.libraries.find(lib => lib.id === existingAudiobook.libraryId)
-
- if (!existingAudiobookLibrary) {
- Logger.error(`[Scanner] Audiobook "${existingAudiobook.title}" found in different library that no longer exists ${existingAudiobook.libraryId}`)
- } else if (existingAudiobook.libraryId !== libraryId) {
- Logger.warn(`[Scanner] Audiobook "${existingAudiobook.title}" found in different library "${existingAudiobookLibrary.name}"`)
- } else {
- Logger.warn(`[Scanner] Audiobook "${existingAudiobook.title}" found in different folder "${existingAudiobook.folderId}" of library "${existingAudiobookLibrary.name}"`)
- }
-
- existingAudiobook.libraryId = libraryId
- existingAudiobook.folderId = folderId
- hasUpdatedLibraryOrFolder = true
- Logger.info(`[Scanner] Updated Audiobook "${existingAudiobook.title}" library and folder to "${libraryId}" "${folderId}"`)
- }
-
- // inode value may change when using shared drives, update inode if matching path is found
- // Note: inode will not change on rename
- var hasUpdatedIno = false
- if (!existingAudiobook) {
- // check an audiobook exists with matching path, then update inodes
- existingAudiobook = this.audiobooks.find(a => a.path === audiobookData.path)
- if (existingAudiobook) {
- var oldIno = existingAudiobook.ino
- existingAudiobook.ino = audiobookData.ino
- Logger.debug(`[Scanner] Scan Audiobook Data: Updated inode from "${oldIno}" to "${existingAudiobook.ino}"`)
- hasUpdatedIno = true
-
- if (existingAudiobook.libraryId !== libraryId || existingAudiobook.folderId !== folderId) {
- Logger.warn(`[Scanner] Audiobook found by path is in a different library or folder, ${existingAudiobook.libraryId}/${existingAudiobook.folderId} should be ${libraryId}/${folderId}`)
-
- existingAudiobook.libraryId = libraryId
- existingAudiobook.folderId = folderId
- hasUpdatedLibraryOrFolder = true
- Logger.info(`[Scanner] Updated Audiobook "${existingAudiobook.title}" library and folder to "${libraryId}" "${folderId}"`)
- }
- }
- }
-
- var scanResult = null
- var finalAudiobook = null
-
- if (existingAudiobook) {
- finalAudiobook = existingAudiobook
-
- scanResult = await this.scanExistingAudiobook(existingAudiobook, audiobookData, hasUpdatedIno, hasUpdatedLibraryOrFolder, forceAudioFileScan)
-
- if (scanResult === ScanResult.REMOVED || scanResult === ScanResult.NOTHING) {
- finalAudiobook = null
- }
- } else {
- finalAudiobook = await this.scanNewAudiobook(audiobookData)
-
- scanResult = finalAudiobook ? ScanResult.ADDED : ScanResult.NOTHING
-
- if (finalAudiobook === ScanResult.NOTHING) {
- finalAudiobook = null
- scanResult = ScanResult.NOTHING
- } else {
- scanResult = ScanResult.ADDED
- }
- }
-
- // Scan for cover if enabled and has no cover
- if (finalAudiobook && scannerFindCovers && !finalAudiobook.cover) {
- if (finalAudiobook.book.shouldSearchForCover) {
- var updatedCover = await this.searchForCover(finalAudiobook)
-
- finalAudiobook.book.updateLastCoverSearch(updatedCover)
-
- if (updatedCover && scanResult === ScanResult.UPTODATE) {
- scanResult = ScanResult.UPDATED
- }
- await this.db.updateAudiobook(finalAudiobook)
- this.emitter('audiobook_updated', finalAudiobook.toJSONMinified())
- } else {
- Logger.debug(`[Scanner] Audiobook "${finalAudiobook.title}" cover already scanned - not re-scanning`)
- }
- }
-
- return scanResult
- }
-
- async scan(libraryId, forceAudioFileScan = false) {
- if (this.librariesScanning.includes(libraryId)) {
- Logger.error(`[Scanner] Already scanning ${libraryId}`)
- return
- }
-
- var library = this.db.libraries.find(lib => lib.id === libraryId)
- if (!library) {
- Logger.error(`[Scanner] Library not found for scan ${libraryId}`)
- return
- } else if (!library.folders.length) {
- Logger.warn(`[Scanner] Library has no folders to scan "${library.name}"`)
- return
- }
-
- var scanPayload = {
- id: libraryId,
- name: library.name,
- folders: library.folders.length
- }
- this.emitter('scan_start', scanPayload)
- Logger.info(`[Scanner] Starting scan of library "${library.name}" with ${library.folders.length} folders`)
-
- library.lastScan = Date.now()
- await this.db.updateEntity('library', library)
-
- this.librariesScanning.push(scanPayload)
-
- var audiobooksInLibrary = this.db.audiobooks.filter(ab => ab.libraryId === libraryId)
-
- // TODO: This temporary fix from pre-release should be removed soon, "checkUpdateInos"
- if (audiobooksInLibrary.length) {
- for (let i = 0; i < audiobooksInLibrary.length; i++) {
- var ab = audiobooksInLibrary[i]
- // Update ino if inos are not set
- var shouldUpdateIno = ab.hasMissingIno
- if (shouldUpdateIno) {
- var filesWithMissingIno = ab.getFilesWithMissingIno()
-
- Logger.debug(`\nUpdating inos for "${ab.title}"`)
- Logger.debug(`In Scan, Files with missing inode`, filesWithMissingIno)
-
- var hasUpdates = await ab.checkUpdateInos()
- if (hasUpdates) {
- await this.db.updateAudiobook(ab)
- }
- }
- }
- }
-
- const scanStart = Date.now()
- var audiobookDataFound = []
- for (let i = 0; i < library.folders.length; i++) {
- var folder = library.folders[i]
- var abDataFoundInFolder = await scanRootDir(folder, this.db.serverSettings)
- Logger.debug(`[Scanner] ${abDataFoundInFolder.length} ab data found in folder "${folder.fullPath}"`)
- audiobookDataFound = audiobookDataFound.concat(abDataFoundInFolder)
- }
-
- // Remove audiobooks with no inode
- audiobookDataFound = audiobookDataFound.filter(abd => abd.ino)
-
- if (this.cancelLibraryScan[libraryId]) {
- Logger.info(`[Scanner] Canceling scan ${libraryId}`)
- delete this.cancelLibraryScan[libraryId]
- this.librariesScanning = this.librariesScanning.filter(l => l.id !== libraryId)
- this.emitter('scan_complete', { id: libraryId, name: library.name, results: null })
- return null
- }
-
- var scanResults = {
- removed: 0,
- updated: 0,
- added: 0,
- missing: 0
- }
-
- // Check for removed audiobooks
- for (let i = 0; i < audiobooksInLibrary.length; i++) {
- var audiobook = audiobooksInLibrary[i]
- var dataFound = audiobookDataFound.find(abd => abd.ino === audiobook.ino || comparePaths(abd.path, audiobook.path))
- if (!dataFound) {
- Logger.info(`[Scanner] Audiobook "${audiobook.title}" is missing`)
- audiobook.isMissing = true
- audiobook.lastUpdate = Date.now()
- scanResults.missing++
- await this.db.updateAudiobook(audiobook)
- this.emitter('audiobook_updated', audiobook.toJSONMinified())
- }
- if (this.cancelLibraryScan[libraryId]) {
- Logger.info(`[Scanner] Canceling scan ${libraryId}`)
- delete this.cancelLibraryScan[libraryId]
- this.librariesScanning = this.librariesScanning.filter(l => l.id !== libraryId)
- this.emitter('scan_complete', { id: libraryId, name: library.name, results: scanResults })
- return
- }
- }
-
- // Check for new and updated audiobooks
- for (let i = 0; i < audiobookDataFound.length; i++) {
- var result = await this.scanAudiobookData(audiobookDataFound[i], forceAudioFileScan)
- if (result === ScanResult.ADDED) scanResults.added++
- if (result === ScanResult.REMOVED) scanResults.removed++
- if (result === ScanResult.UPDATED) scanResults.updated++
-
- var progress = Math.round(100 * (i + 1) / audiobookDataFound.length)
- this.emitter('scan_progress', {
- id: libraryId,
- name: library.name,
- progress: {
- total: audiobookDataFound.length,
- done: i + 1,
- progress
- }
- })
- if (this.cancelLibraryScan[libraryId]) {
- Logger.info(`[Scanner] Canceling scan ${libraryId}`)
- delete this.cancelLibraryScan[libraryId]
- break
- }
- }
- const scanElapsed = Math.floor((Date.now() - scanStart) / 1000)
- Logger.info(`[Scanned] Finished | ${scanResults.added} added | ${scanResults.updated} updated | ${scanResults.removed} removed | ${scanResults.missing} missing | elapsed: ${secondsToTimestamp(scanElapsed)}`)
- this.librariesScanning = this.librariesScanning.filter(l => l.id !== libraryId)
- this.emitter('scan_complete', { id: libraryId, name: library.name, results: scanResults })
- }
-
- async scanAudiobookById(audiobookId) {
- const audiobook = this.db.audiobooks.find(ab => ab.id === audiobookId)
- if (!audiobook) {
- Logger.error(`[Scanner] Scan audiobook by id not found ${audiobookId}`)
- return ScanResult.NOTHING
- }
- const library = this.db.libraries.find(lib => lib.id === audiobook.libraryId)
- if (!library) {
- Logger.error(`[Scanner] Scan audiobook by id library not found "${audiobook.libraryId}"`)
- return ScanResult.NOTHING
- }
- const folder = library.folders.find(f => f.id === audiobook.folderId)
- if (!folder) {
- Logger.error(`[Scanner] Scan audiobook by id folder not found "${audiobook.folderId}" in library "${library.name}"`)
- return ScanResult.NOTHING
- }
- if (!folder.libraryId) {
- Logger.fatal(`[Scanner] Folder does not have a library id set...`, folder)
- return ScanResult.NOTHING
- }
-
- Logger.info(`[Scanner] Scanning Audiobook "${audiobook.title}"`)
- return this.scanAudiobook(folder, audiobook.fullPath, true)
- }
-
- async scanAudiobook(folder, audiobookFullPath, forceAudioFileScan = false) {
- Logger.debug('[Scanner] scanAudiobook', audiobookFullPath)
- var audiobookData = await getAudiobookFileData(folder, audiobookFullPath, this.db.serverSettings)
- if (!audiobookData) {
- return ScanResult.NOTHING
- }
- return this.scanAudiobookData(audiobookData, forceAudioFileScan)
- }
-
- async scanFolderUpdates(libraryId, folderId, fileUpdateBookGroup) {
- var library = this.db.libraries.find(lib => lib.id === libraryId)
- if (!library) {
- Logger.error(`[Scanner] Library "${libraryId}" not found for scan library updates`)
- return null
- }
- var folder = library.folders.find(f => f.id === folderId)
- if (!folder) {
- Logger.error(`[Scanner] Folder "${folderId}" not found in library "${library.name}" for scan library updates`)
- return null
- }
-
- Logger.debug(`[Scanner] Scanning file update groups in folder "${folder.id}" of library "${library.name}"`)
-
- var bookGroupingResults = {}
- for (const bookDir in fileUpdateBookGroup) {
- var fullPath = Path.posix.join(folder.fullPath.replace(/\\/g, '/'), bookDir)
-
- // Check if book dir group is already an audiobook or in a subdir of an audiobook
- var existingAudiobook = this.db.audiobooks.find(ab => fullPath.startsWith(ab.fullPath))
- if (existingAudiobook) {
-
- // Is the audiobook exactly - check if was deleted
- if (existingAudiobook.fullPath === fullPath) {
- var exists = await fs.pathExists(fullPath)
- if (!exists) {
- Logger.info(`[Scanner] Scanning file update group and audiobook was deleted "${existingAudiobook.title}" - marking as missing`)
- existingAudiobook.isMissing = true
- existingAudiobook.lastUpdate = Date.now()
- await this.db.updateAudiobook(existingAudiobook)
- this.emitter('audiobook_updated', existingAudiobook.toJSONMinified())
-
- bookGroupingResults[bookDir] = ScanResult.REMOVED
- continue;
- }
- }
-
- // Scan audiobook for updates
- Logger.debug(`[Scanner] Folder update for relative path "${bookDir}" is in audiobook "${existingAudiobook.title}" - scan for updates`)
- bookGroupingResults[bookDir] = await this.scanAudiobook(folder, existingAudiobook.fullPath)
- continue;
- }
-
- // Check if an audiobook is a subdirectory of this dir
- var childAudiobook = this.db.audiobooks.find(ab => ab.fullPath.startsWith(fullPath))
- if (childAudiobook) {
- Logger.warn(`[Scanner] Files were modified in a parent directory of an audiobook "${childAudiobook.title}" - ignoring`)
- bookGroupingResults[bookDir] = ScanResult.NOTHING
- continue;
- }
-
- Logger.debug(`[Scanner] Folder update group must be a new book "${bookDir}" in library "${library.name}"`)
- bookGroupingResults[bookDir] = await this.scanAudiobook(folder, fullPath)
- }
-
- return bookGroupingResults
- }
-
- // Array of file update objects that may have been renamed, removed or added
- async filesChanged(fileUpdates) {
- if (!fileUpdates.length) return null
-
- // Group files by folder
- var folderGroups = {}
- fileUpdates.forEach((file) => {
- if (folderGroups[file.folderId]) {
- folderGroups[file.folderId].fileUpdates.push(file)
- } else {
- folderGroups[file.folderId] = {
- libraryId: file.libraryId,
- folderId: file.folderId,
- fileUpdates: [file]
- }
- }
- })
-
- const libraryScanResults = {}
-
- // Group files by book
- for (const folderId in folderGroups) {
- var libraryId = folderGroups[folderId].libraryId
- var library = this.db.libraries.find(lib => lib.id === libraryId)
- if (!library) {
- Logger.error(`[Scanner] Library not found in files changed ${libraryId}`)
- continue;
- }
- var folder = library.getFolderById(folderId)
- if (!folder) {
- Logger.error(`[Scanner] Folder is not in library in files changed "${folderId}", Library "${library.name}"`)
- continue;
- }
-
- var relFilePaths = folderGroups[folderId].fileUpdates.map(fileUpdate => fileUpdate.relPath)
- var fileUpdateBookGroup = groupFilesIntoAudiobookPaths(relFilePaths, true)
- var folderScanResults = await this.scanFolderUpdates(libraryId, folderId, fileUpdateBookGroup)
- libraryScanResults[libraryId] = folderScanResults
- }
-
- Logger.debug(`[Scanner] Finished scanning file changes, results:`, libraryScanResults)
- return libraryScanResults
- }
-
- async saveMetadata(audiobookId) {
- if (audiobookId) {
- var audiobook = this.db.audiobooks.find(ab => ab.id === audiobookId)
- if (!audiobook) {
- return {
- error: 'Audiobook not found'
- }
- }
- var savedPath = await audiobook.writeNfoFile()
- return {
- audiobookId,
- audiobookTitle: audiobook.title,
- savedPath
- }
- } else {
- var response = {
- success: 0,
- failed: 0
- }
- for (let i = 0; i < this.db.audiobooks.length; i++) {
- var audiobook = this.db.audiobooks[i]
- var savedPath = await audiobook.writeNfoFile()
- if (savedPath) {
- Logger.info(`[Scanner] Saved metadata nfo ${savedPath}`)
- response.success++
- } else {
- response.failed++
- }
- }
- return response
- }
- }
-
- async find(req, res) {
- var method = req.params.method
- var query = req.query
-
- var result = null
-
- if (method === 'isbn') {
- result = await this.bookFinder.findByISBN(query)
- } else if (method === 'search') {
- result = await this.bookFinder.search(query.provider, query.title, query.author || null)
- }
-
- res.json(result)
- }
-
- async findCovers(req, res) {
- var query = req.query
- var options = {
- fallbackTitleOnly: !!query.fallbackTitleOnly
- }
- var result = await this.bookFinder.findCovers(query.provider, query.title, query.author || null, options)
- res.json(result)
- }
-
- async fixDuplicateIds() {
- var ids = {}
- var audiobooksUpdated = 0
- for (let i = 0; i < this.db.audiobooks.length; i++) {
- var ab = this.db.audiobooks[i]
- if (ids[ab.id]) {
- var abCopy = new Audiobook(ab.toJSON())
- abCopy.id = getId('ab')
- if (abCopy.book.cover) {
- abCopy.book.cover = abCopy.book.cover.replace(ab.id, abCopy.id)
- }
- Logger.warn('Found duplicate ID - updating from', ab.id, 'to', abCopy.id)
- await this.db.removeEntity('audiobook', ab.id)
- await this.db.insertEntity('audiobook', abCopy)
- audiobooksUpdated++
- } else {
- ids[ab.id] = true
- }
- }
- if (audiobooksUpdated) {
- Logger.info(`[Scanner] Updated ${audiobooksUpdated} audiobook IDs`)
- }
- }
-}
-module.exports = Scanner
\ No newline at end of file
diff --git a/server/Server.js b/server/Server.js
index 08f6a16e..21ec5504 100644
--- a/server/Server.js
+++ b/server/Server.js
@@ -17,8 +17,7 @@ const Logger = require('./Logger')
// Classes
const Auth = require('./Auth')
const Watcher = require('./Watcher')
-const Scanner = require('./Scanner')
-const Scanner2 = require('./scanner/Scanner')
+const Scanner = require('./scanner/Scanner')
const Db = require('./Db')
const BackupManager = require('./BackupManager')
const LogManager = require('./LogManager')
@@ -52,13 +51,12 @@ class Server {
this.watcher = new Watcher(this.AudiobookPath)
this.coverController = new CoverController(this.db, this.cacheManager, this.MetadataPath, this.AudiobookPath)
this.scanner = new Scanner(this.AudiobookPath, this.MetadataPath, this.db, this.coverController, this.emitter.bind(this))
- this.scanner2 = new Scanner2(this.AudiobookPath, this.MetadataPath, this.db, this.coverController, this.emitter.bind(this))
this.streamManager = new StreamManager(this.db, this.MetadataPath, this.emitter.bind(this), this.clientEmitter.bind(this))
this.rssFeeds = new RssFeeds(this.Port, this.db)
this.downloadManager = new DownloadManager(this.db, this.MetadataPath, this.AudiobookPath, this.emitter.bind(this))
- this.apiController = new ApiController(this.MetadataPath, this.db, this.scanner, this.auth, this.streamManager, this.rssFeeds, this.downloadManager, this.coverController, this.backupManager, this.watcher, this.cacheManager, this.emitter.bind(this), this.clientEmitter.bind(this))
- this.hlsController = new HlsController(this.db, this.scanner, this.auth, this.streamManager, this.emitter.bind(this), this.streamManager.StreamsPath)
+ this.apiController = new ApiController(this.MetadataPath, this.db, this.auth, this.streamManager, this.rssFeeds, this.downloadManager, this.coverController, this.backupManager, this.watcher, this.cacheManager, this.emitter.bind(this), this.clientEmitter.bind(this))
+ this.hlsController = new HlsController(this.db, this.auth, this.streamManager, this.emitter.bind(this), this.streamManager.StreamsPath)
Logger.logManager = this.logManager
@@ -311,18 +309,18 @@ class Server {
async filesChanged(fileUpdates) {
Logger.info('[Server]', fileUpdates.length, 'Files Changed')
- await this.scanner2.scanFilesChanged(fileUpdates)
+ await this.scanner.scanFilesChanged(fileUpdates)
}
async scan(libraryId, options = {}) {
Logger.info('[Server] Starting Scan')
- await this.scanner2.scan(libraryId, options)
+ await this.scanner.scan(libraryId, options)
// await this.scanner.scan(libraryId)
Logger.info('[Server] Scan complete')
}
async scanAudiobook(socket, audiobookId) {
- var result = await this.scanner2.scanAudiobookById(audiobookId)
+ var result = await this.scanner.scanAudiobookById(audiobookId)
var scanResultName = ''
for (const key in ScanResult) {
if (ScanResult[key] === result) {
@@ -334,7 +332,7 @@ class Server {
cancelScan(id) {
Logger.debug('[Server] Cancel scan', id)
- this.scanner2.setCancelLibraryScan(id)
+ this.scanner.setCancelLibraryScan(id)
}
// Generates an NFO metadata file, if no audiobookId is passed then all audiobooks are done
@@ -623,7 +621,7 @@ class Server {
configPath: this.ConfigPath,
user: client.user.toJSONForBrowser(),
stream: client.stream || null,
- librariesScanning: this.scanner2.librariesScanning,
+ librariesScanning: this.scanner.librariesScanning,
backups: (this.backupManager.backups || []).map(b => b.toJSON())
}
if (user.type === 'root') {
diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js
index 9699e202..f0963cef 100644
--- a/server/controllers/LibraryController.js
+++ b/server/controllers/LibraryController.js
@@ -124,7 +124,8 @@ class LibraryController {
page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0,
sortBy: req.query.sort,
sortDesc: req.query.desc === '1',
- filterBy: req.query.filter
+ filterBy: req.query.filter,
+ minified: req.query.minified === '1'
}
if (payload.filterBy) {
@@ -147,7 +148,7 @@ class LibraryController {
var startIndex = payload.page * payload.limit
audiobooks = audiobooks.slice(startIndex, startIndex + payload.limit)
}
- payload.results = audiobooks.map(ab => ab.toJSONExpanded())
+ payload.results = audiobooks.map(ab => payload.minified ? ab.toJSONMinified() : ab.toJSONExpanded())
res.json(payload)
}
@@ -162,10 +163,11 @@ class LibraryController {
page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0,
sortBy: req.query.sort,
sortDesc: req.query.desc === '1',
- filterBy: req.query.filter
+ filterBy: req.query.filter,
+ minified: req.query.minified === '1'
}
- var series = libraryHelpers.getSeriesFromBooks(audiobooks)
+ var series = libraryHelpers.getSeriesFromBooks(audiobooks, payload.minified)
payload.total = series.length
if (payload.limit) {
@@ -208,10 +210,11 @@ class LibraryController {
page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0,
sortBy: req.query.sort,
sortDesc: req.query.desc === '1',
- filterBy: req.query.filter
+ filterBy: req.query.filter,
+ minified: req.query.minified === '1'
}
- var collections = this.db.collections.filter(c => c.libraryId === req.library.id).map(c => c.toJSONExpanded(audiobooks))
+ var collections = this.db.collections.filter(c => c.libraryId === req.library.id).map(c => c.toJSONExpanded(audiobooks, payload.minified))
payload.total = collections.length
if (payload.limit) {
@@ -235,28 +238,29 @@ class LibraryController {
var library = req.library
var books = this.db.audiobooks.filter(ab => ab.libraryId === library.id)
var limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 12
+ var minified = req.query.minified === '1'
var booksWithUserAb = libraryHelpers.getBooksWithUserAudiobook(req.user, books)
- var series = libraryHelpers.getSeriesFromBooks(books)
+ var series = libraryHelpers.getSeriesFromBooks(books, minified)
var categories = [
{
id: 'continue-reading',
label: 'Continue Reading',
type: 'books',
- entities: libraryHelpers.getBooksMostRecentlyRead(booksWithUserAb, limitPerShelf)
+ entities: libraryHelpers.getBooksMostRecentlyRead(booksWithUserAb, limitPerShelf, minified)
},
{
id: 'recently-added',
label: 'Recently Added',
type: 'books',
- entities: libraryHelpers.getBooksMostRecentlyAdded(books, limitPerShelf)
+ entities: libraryHelpers.getBooksMostRecentlyAdded(books, limitPerShelf, minified)
},
{
id: 'read-again',
label: 'Read Again',
type: 'books',
- entities: libraryHelpers.getBooksMostRecentlyFinished(booksWithUserAb, limitPerShelf)
+ entities: libraryHelpers.getBooksMostRecentlyFinished(booksWithUserAb, limitPerShelf, minified)
},
{
id: 'recent-series',
diff --git a/server/objects/AudioFile.js b/server/objects/AudioFile.js
index 99edac14..2e33f0ef 100644
--- a/server/objects/AudioFile.js
+++ b/server/objects/AudioFile.js
@@ -121,40 +121,6 @@ class AudioFile {
}
}
- setData(data) {
- this.index = data.index || null
- this.ino = data.ino || null
- this.filename = data.filename
- this.ext = data.ext
- this.path = data.path
- this.fullPath = data.fullPath
- this.addedAt = Date.now()
-
- this.trackNumFromMeta = data.trackNumFromMeta
- this.trackNumFromFilename = data.trackNumFromFilename
- this.cdNumFromFilename = data.cdNumFromFilename
-
- this.manuallyVerified = !!data.manuallyVerified
- this.invalid = !!data.invalid
- this.exclude = !!data.exclude
- this.error = data.error || null
-
- this.format = data.format
- this.duration = data.duration
- this.size = data.size
- this.bitRate = data.bit_rate || null
- this.language = data.language
- this.codec = data.codec || null
- this.timeBase = data.time_base
- this.channels = data.channels
- this.channelLayout = data.channel_layout
- this.chapters = data.chapters || []
- this.embeddedCoverArt = data.embedded_cover_art || null
-
- this.metadata = new AudioFileMetadata()
- this.metadata.setData(data)
- }
-
// New scanner creates AudioFile from AudioFileScanner
setDataFromProbe(fileData, probeData) {
this.index = fileData.index || null
@@ -224,48 +190,6 @@ class AudioFile {
return hasUpdates
}
- // Called from audioFileScanner.js with scanData
- updateMetadata(data) {
- if (!this.metadata) this.metadata = new AudioFileMetadata()
-
- var dataMap = {
- format: data.format,
- duration: data.duration,
- size: data.size,
- bitRate: data.bit_rate || null,
- language: data.language,
- codec: data.codec || null,
- timeBase: data.time_base,
- channels: data.channels,
- channelLayout: data.channel_layout,
- chapters: data.chapters || [],
- embeddedCoverArt: data.embedded_cover_art || null,
- trackNumFromMeta: data.trackNumFromMeta,
- trackNumFromFilename: data.trackNumFromFilename,
- cdNumFromFilename: data.cdNumFromFilename
- }
-
- var hasUpdates = false
- for (const key in dataMap) {
- if (key === 'chapters') {
- var chaptersUpdated = this.syncChapters(dataMap.chapters)
- if (chaptersUpdated) {
- hasUpdates = true
- }
- } else if (dataMap[key] !== this[key]) {
- // Logger.debug(`[AudioFile] "${key}" from ${this[key]} => ${dataMap[key]}`)
- this[key] = dataMap[key]
- hasUpdates = true
- }
- }
-
- if (this.metadata.updateData(data)) {
- hasUpdates = true
- }
-
- return hasUpdates
- }
-
clone() {
return new AudioFile(this.toJSON())
}
diff --git a/server/objects/Audiobook.js b/server/objects/Audiobook.js
index 28fb67e7..3483317e 100644
--- a/server/objects/Audiobook.js
+++ b/server/objects/Audiobook.js
@@ -211,7 +211,7 @@ class Audiobook {
ebooks: this.ebooks.map(ebook => ebook.toJSON()),
numEbooks: this.ebooks.length,
numTracks: this.tracks.length,
- chapters: this.chapters || [],
+ numChapters: (this.chapters || []).length,
isMissing: !!this.isMissing,
isInvalid: !!this.isInvalid,
hasMissingParts: this.numMissingParts,
@@ -402,15 +402,8 @@ class Audiobook {
}
addAudioFile(audioFileData) {
- if (audioFileData instanceof AudioFile) {
- this.audioFiles.push(audioFileData)
- return audioFileData
- } else {
- var audioFile = new AudioFile()
- audioFile.setData(audioFileData)
- this.audioFiles.push(audioFile)
- return audioFile
- }
+ this.audioFiles.push(audioFileData)
+ return audioFileData
}
updateAudioFile(updatedAudioFile) {
diff --git a/server/objects/Stream.js b/server/objects/Stream.js
index 033b17e1..210f40de 100644
--- a/server/objects/Stream.js
+++ b/server/objects/Stream.js
@@ -129,7 +129,7 @@ class Stream extends EventEmitter {
id: this.id,
clientId: this.client.id,
userId: this.client.user.id,
- audiobook: this.audiobook.toJSONMinified(),
+ audiobook: this.audiobook.toJSONExpanded(),
segmentLength: this.segmentLength,
playlistPath: this.playlistPath,
clientPlaylistUri: this.clientPlaylistUri,
diff --git a/server/objects/UserCollection.js b/server/objects/UserCollection.js
index be761d85..780b2a42 100644
--- a/server/objects/UserCollection.js
+++ b/server/objects/UserCollection.js
@@ -37,11 +37,11 @@ class UserCollection {
}
}
- toJSONExpanded(audiobooks) {
+ toJSONExpanded(audiobooks, minifiedBooks = false) {
var json = this.toJSON()
json.books = json.books.map(bookId => {
var _ab = audiobooks.find(ab => ab.id === bookId)
- return _ab ? _ab.toJSONExpanded() : null
+ return _ab ? minifiedBooks ? _ab.toJSONMinified() : _ab.toJSONExpanded() : null
}).filter(b => !!b)
return json
}
diff --git a/server/scanner/AudioFileScanner.js b/server/scanner/AudioFileScanner.js
index c217f661..94311208 100644
--- a/server/scanner/AudioFileScanner.js
+++ b/server/scanner/AudioFileScanner.js
@@ -68,7 +68,7 @@ class AudioFileScanner {
async scan(audioFileData, bookScanData, verbose = false) {
var probeStart = Date.now()
// Logger.debug(`[AudioFileScanner] Start Probe ${audioFileData.fullPath}`)
- var probeData = await prober.probe2(audioFileData.fullPath, verbose)
+ var probeData = await prober.probe(audioFileData.fullPath, verbose)
if (probeData.error) {
Logger.error(`[AudioFileScanner] ${probeData.error} : "${audioFileData.fullPath}"`)
return null
diff --git a/server/scanner/AudioProbeData.js b/server/scanner/AudioProbeData.js
index 82efa6fa..0fd294ec 100644
--- a/server/scanner/AudioProbeData.js
+++ b/server/scanner/AudioProbeData.js
@@ -21,20 +21,13 @@ class AudioProbeData {
this.trackTotal = null
}
- getDefaultAudioStream(audioStreams) {
- if (audioStreams.length === 1) return audioStreams[0]
- var defaultStream = audioStreams.find(a => a.is_default)
- if (!defaultStream) return audioStreams[0]
- return defaultStream
- }
-
getEmbeddedCoverArt(videoStream) {
const ImageCodecs = ['mjpeg', 'jpeg', 'png']
return ImageCodecs.includes(videoStream.codec) ? videoStream.codec : null
}
setData(data) {
- var audioStream = this.getDefaultAudioStream(data.audio_streams)
+ var audioStream = data.audio_stream
this.embeddedCoverArt = data.video_stream ? this.getEmbeddedCoverArt(data.video_stream) : null
this.format = data.format
this.duration = data.duration
diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js
index 31d9ba3f..177d5db0 100644
--- a/server/scanner/Scanner.js
+++ b/server/scanner/Scanner.js
@@ -539,5 +539,63 @@ class Scanner {
}
return false
}
+
+ async saveMetadata(audiobookId) {
+ if (audiobookId) {
+ var audiobook = this.db.audiobooks.find(ab => ab.id === audiobookId)
+ if (!audiobook) {
+ return {
+ error: 'Audiobook not found'
+ }
+ }
+ var savedPath = await audiobook.writeNfoFile()
+ return {
+ audiobookId,
+ audiobookTitle: audiobook.title,
+ savedPath
+ }
+ } else {
+ var response = {
+ success: 0,
+ failed: 0
+ }
+ for (let i = 0; i < this.db.audiobooks.length; i++) {
+ var audiobook = this.db.audiobooks[i]
+ var savedPath = await audiobook.writeNfoFile()
+ if (savedPath) {
+ Logger.info(`[Scanner] Saved metadata nfo ${savedPath}`)
+ response.success++
+ } else {
+ response.failed++
+ }
+ }
+ return response
+ }
+ }
+
+ // TEMP: Old version created ids that had a chance of repeating
+ async fixDuplicateIds() {
+ var ids = {}
+ var audiobooksUpdated = 0
+ for (let i = 0; i < this.db.audiobooks.length; i++) {
+ var ab = this.db.audiobooks[i]
+ if (ids[ab.id]) {
+ var abCopy = new Audiobook(ab.toJSON())
+ abCopy.id = getId('ab')
+ if (abCopy.book.cover) {
+ abCopy.book.cover = abCopy.book.cover.replace(ab.id, abCopy.id)
+ }
+ Logger.warn('Found duplicate ID - updating from', ab.id, 'to', abCopy.id)
+ await this.db.removeEntity('audiobook', ab.id)
+ await this.db.insertEntity('audiobook', abCopy)
+ audiobooksUpdated++
+ } else {
+ ids[ab.id] = true
+ }
+ }
+ if (audiobooksUpdated) {
+ Logger.info(`[Scanner] Updated ${audiobooksUpdated} audiobook IDs`)
+ }
+ }
}
module.exports = Scanner
\ No newline at end of file
diff --git a/server/utils/audioFileScanner.js b/server/utils/audioFileScanner.js
deleted file mode 100644
index 05d4d8e4..00000000
--- a/server/utils/audioFileScanner.js
+++ /dev/null
@@ -1,317 +0,0 @@
-const Path = require('path')
-const Logger = require('../Logger')
-const prober = require('./prober')
-
-const ImageCodecs = ['mjpeg', 'jpeg', 'png']
-
-function getDefaultAudioStream(audioStreams) {
- if (audioStreams.length === 1) return audioStreams[0]
- var defaultStream = audioStreams.find(a => a.is_default)
- if (!defaultStream) return audioStreams[0]
- return defaultStream
-}
-
-async function scan(path, verbose = false) {
- Logger.debug(`Scanning path "${path}"`)
- var probeData = await prober.probe(path, verbose)
- if (!probeData || !probeData.audio_streams || !probeData.audio_streams.length) {
- return {
- error: 'Invalid audio file'
- }
- }
- if (!probeData.duration || !probeData.size) {
- return {
- error: 'Invalid duration or size'
- }
- }
- var audioStream = getDefaultAudioStream(probeData.audio_streams)
-
- const finalData = {
- format: probeData.format,
- duration: probeData.duration,
- size: probeData.size,
- bit_rate: audioStream.bit_rate || probeData.bit_rate,
- codec: audioStream.codec,
- time_base: audioStream.time_base,
- language: audioStream.language,
- channel_layout: audioStream.channel_layout,
- channels: audioStream.channels,
- sample_rate: audioStream.sample_rate,
- chapters: probeData.chapters || []
- }
-
- var hasCoverArt = probeData.video_stream ? ImageCodecs.includes(probeData.video_stream.codec) : false
- if (hasCoverArt) {
- finalData.embedded_cover_art = probeData.video_stream.codec
- }
-
- for (const key in probeData) {
- if (probeData[key] && key.startsWith('file_tag')) {
- finalData[key] = probeData[key]
- }
- }
-
- if (finalData.file_tag_track) {
- var track = finalData.file_tag_track
- var trackParts = track.split('/').map(part => Number(part))
- if (trackParts.length > 0) {
- finalData.trackNumber = trackParts[0]
- }
- if (trackParts.length > 1) {
- finalData.trackTotal = trackParts[1]
- }
- }
-
- if (verbose && probeData.rawTags) {
- finalData.rawTags = probeData.rawTags
- }
-
- return finalData
-}
-module.exports.scan = scan
-
-
-function isNumber(val) {
- return !isNaN(val) && val !== null
-}
-
-function getTrackNumberFromMeta(scanData) {
- return !isNaN(scanData.trackNumber) && scanData.trackNumber !== null ? Math.trunc(Number(scanData.trackNumber)) : null
-}
-
-function getTrackNumberFromFilename(title, author, series, publishYear, filename) {
- var partbasename = Path.basename(filename, Path.extname(filename))
-
- // Remove title, author, series, and publishYear from filename if there
- if (title) partbasename = partbasename.replace(title, '')
- if (author) partbasename = partbasename.replace(author, '')
- if (series) partbasename = partbasename.replace(series, '')
- if (publishYear) partbasename = partbasename.replace(publishYear)
-
- // Remove eg. "disc 1" from path
- partbasename = partbasename.replace(/\bdisc \d\d?\b/i, '')
-
- // Remove "cd01" or "cd 01" from path
- partbasename = partbasename.replace(/\bcd ?\d\d?\b/i, '')
-
- var numbersinpath = partbasename.match(/\d{1,4}/g)
- if (!numbersinpath) return null
-
- var number = numbersinpath.length ? parseInt(numbersinpath[0]) : null
- return number
-}
-
-function getCdNumberFromFilename(title, author, series, publishYear, filename) {
- var partbasename = Path.basename(filename, Path.extname(filename))
-
- // Remove title, author, series, and publishYear from filename if there
- if (title) partbasename = partbasename.replace(title, '')
- if (author) partbasename = partbasename.replace(author, '')
- if (series) partbasename = partbasename.replace(series, '')
- if (publishYear) partbasename = partbasename.replace(publishYear)
-
- var cdNumber = null
-
- var cdmatch = partbasename.match(/\b(disc|cd) ?(\d\d?)\b/i)
- if (cdmatch && cdmatch.length > 2 && cdmatch[2]) {
- if (!isNaN(cdmatch[2])) {
- cdNumber = Number(cdmatch[2])
- }
- }
-
- return cdNumber
-}
-
-async function scanAudioFiles(audiobook, newAudioFiles) {
- if (!newAudioFiles || !newAudioFiles.length) {
- Logger.error('[AudioFileScanner] Scan Audio Files no new files', audiobook.title)
- return
- }
-
- Logger.debug('[AudioFileScanner] Scanning audio files')
-
- var tracks = []
- var numDuplicateTracks = 0
- var numInvalidTracks = 0
-
- for (let i = 0; i < newAudioFiles.length; i++) {
- var audioFile = newAudioFiles[i]
- var scanData = await scan(audioFile.fullPath)
- if (!scanData || scanData.error) {
- Logger.error('[AudioFileScanner] Scan failed for', audioFile.path)
- continue;
- }
-
- var trackNumFromMeta = getTrackNumberFromMeta(scanData)
- var book = audiobook.book || {}
-
- var trackNumFromFilename = getTrackNumberFromFilename(book.title, book.author, book.series, book.publishYear, audioFile.filename)
-
- var cdNumFromFilename = getCdNumberFromFilename(book.title, book.author, book.series, book.publishYear, audioFile.filename)
-
- // IF CD num was found but no track num - USE cd num as track num
- if (!trackNumFromFilename && cdNumFromFilename) {
- trackNumFromFilename = cdNumFromFilename
- cdNumFromFilename = null
- }
-
- var audioFileObj = {
- ino: audioFile.ino,
- filename: audioFile.filename,
- path: audioFile.path,
- fullPath: audioFile.fullPath,
- ext: audioFile.ext,
- ...scanData,
- trackNumFromMeta,
- trackNumFromFilename,
- cdNumFromFilename
- }
- var audioFile = audiobook.addAudioFile(audioFileObj)
-
- var trackNumber = 1
- if (newAudioFiles.length > 1) {
- trackNumber = isNumber(trackNumFromMeta) ? trackNumFromMeta : trackNumFromFilename
- if (trackNumber === null) {
- Logger.debug('[AudioFileScanner] Invalid track number for', audioFile.filename)
- audioFile.invalid = true
- audioFile.error = 'Failed to get track number'
- numInvalidTracks++
- continue;
- }
- }
-
- if (tracks.find(t => t.index === trackNumber)) {
- // Logger.debug('[AudioFileScanner] Duplicate track number for', audioFile.filename)
- audioFile.invalid = true
- audioFile.error = 'Duplicate track number'
- numDuplicateTracks++
- continue;
- }
-
- audioFile.index = trackNumber
- tracks.push(audioFile)
- }
-
- if (!tracks.length) {
- Logger.warn('[AudioFileScanner] No Tracks for audiobook', audiobook.id)
- return
- }
-
- if (numDuplicateTracks > 0) {
- Logger.warn(`[AudioFileScanner] ${numDuplicateTracks} Duplicate tracks for "${audiobook.title}"`)
- }
- if (numInvalidTracks > 0) {
- Logger.error(`[AudioFileScanner] ${numDuplicateTracks} Invalid tracks for "${audiobook.title}"`)
- }
-
- tracks.sort((a, b) => a.index - b.index)
-
- audiobook.audioFiles.sort((a, b) => {
- var aNum = isNumber(a.trackNumFromMeta) ? a.trackNumFromMeta : isNumber(a.trackNumFromFilename) ? a.trackNumFromFilename : 0
- var bNum = isNumber(b.trackNumFromMeta) ? b.trackNumFromMeta : isNumber(b.trackNumFromFilename) ? b.trackNumFromFilename : 0
- return aNum - bNum
- })
-
- // If first index is 0, increment all by 1
- if (tracks[0].index === 0) {
- tracks = tracks.map(t => {
- t.index += 1
- return t
- })
- }
-
- var hasTracksAlready = audiobook.tracks.length
- tracks.forEach((track) => {
- audiobook.addTrack(track)
- })
- if (hasTracksAlready) {
- audiobook.tracks.sort((a, b) => a.index - b.index)
- }
-}
-module.exports.scanAudioFiles = scanAudioFiles
-
-
-async function rescanAudioFiles(audiobook) {
- var audioFiles = audiobook.audioFiles
- var updates = 0
-
- for (let i = 0; i < audioFiles.length; i++) {
- var audioFile = audioFiles[i]
- var scanData = await scan(audioFile.fullPath)
- if (!scanData || scanData.error) {
- Logger.error('[AudioFileScanner] Scan failed for', audioFile.path)
- // audiobook.invalidAudioFiles.push(parts[i])
- continue;
- }
-
- var trackNumFromMeta = getTrackNumberFromMeta(scanData)
- var book = audiobook.book || {}
-
- var trackNumFromFilename = getTrackNumberFromFilename(book.title, book.author, book.series, book.publishYear, audioFile.filename)
-
- var cdNumFromFilename = getCdNumberFromFilename(book.title, book.author, book.series, book.publishYear, audioFile.filename)
-
- // IF CD num was found but no track num - USE cd num as track num
- if (!trackNumFromFilename && cdNumFromFilename) {
- trackNumFromFilename = cdNumFromFilename
- cdNumFromFilename = null
- }
-
- var metadataUpdate = {
- ...scanData,
- trackNumFromMeta,
- trackNumFromFilename,
- cdNumFromFilename
- }
- var hasUpdates = audioFile.updateMetadata(metadataUpdate)
- if (hasUpdates) {
- // Sync audio track with audio file
- var matchingAudioTrack = audiobook.tracks.find(t => t.ino === audioFile.ino)
- if (matchingAudioTrack) {
- matchingAudioTrack.syncMetadata(audioFile)
- } else if (!audioFile.exclude) { // If audio file is not excluded then it should have an audio track
-
- // Fallback to checking path
- matchingAudioTrack = audiobook.tracks.find(t => t.path === audioFile.path)
- if (matchingAudioTrack) {
- Logger.error(`[AudioFileScanner] Audio File mismatch ino with audio track "${audioFile.filename}"`)
- matchingAudioTrack.ino = audioFile.ino
- matchingAudioTrack.syncMetadata(audioFile)
- } else {
- Logger.error(`[AudioFileScanner] Audio File has no matching Track ${audioFile.filename} for "${audiobook.title}"`)
-
- // Exclude audio file to prevent further errors
- // audioFile.exclude = true
- }
- }
- updates++
- }
- }
-
- return updates
-}
-module.exports.rescanAudioFiles = rescanAudioFiles
-
-async function scanTrackNumbers(audiobook) {
- var tracks = audiobook.tracks || []
- var scannedTrackNumData = []
- for (let i = 0; i < tracks.length; i++) {
- var track = tracks[i]
- var scanData = await scan(track.fullPath, true)
-
- var trackNumFromMeta = getTrackNumberFromMeta(scanData)
- var book = audiobook.book || {}
- var trackNumFromFilename = getTrackNumberFromFilename(book.title, book.author, book.series, book.publishYear, track.filename)
- Logger.info(`[AudioFileScanner] Track # for "${track.filename}", Metadata: "${trackNumFromMeta}", Filename: "${trackNumFromFilename}", Current: "${track.index}"`)
- scannedTrackNumData.push({
- filename: track.filename,
- currentTrackNum: track.index,
- trackNumFromFilename,
- trackNumFromMeta,
- scanDataTrackNum: scanData.file_tag_track,
- rawTags: scanData.rawTags || null
- })
- }
- return scannedTrackNumData
-}
-module.exports.scanTrackNumbers = scanTrackNumbers
\ No newline at end of file
diff --git a/server/utils/libraryHelpers.js b/server/utils/libraryHelpers.js
index 11787580..10bb4af4 100644
--- a/server/utils/libraryHelpers.js
+++ b/server/utils/libraryHelpers.js
@@ -74,19 +74,20 @@ module.exports = {
return data
},
- getSeriesFromBooks(books) {
+ getSeriesFromBooks(books, minified = false) {
var _series = {}
books.forEach((audiobook) => {
if (audiobook.book.series) {
+ var abJson = minified ? audiobook.toJSONMinified() : audiobook.toJSONExpanded()
if (!_series[audiobook.book.series]) {
_series[audiobook.book.series] = {
id: audiobook.book.series,
name: audiobook.book.series,
type: 'series',
- books: [audiobook.toJSONExpanded()]
+ books: [abJson]
}
} else {
- _series[audiobook.book.series].books.push(audiobook.toJSONExpanded())
+ _series[audiobook.book.series].books.push(abJson)
}
}
})
@@ -108,25 +109,25 @@ module.exports = {
})
},
- getBooksMostRecentlyRead(booksWithUserAb, limit) {
+ getBooksMostRecentlyRead(booksWithUserAb, limit, minified = false) {
var booksWithProgress = booksWithUserAb.filter((data) => data.userAudiobook && data.userAudiobook.progress > 0 && !data.userAudiobook.isRead)
booksWithProgress.sort((a, b) => {
return b.userAudiobook.lastUpdate - a.userAudiobook.lastUpdate
})
- return booksWithProgress.map(b => b.book.toJSONExpanded()).slice(0, limit)
+ return booksWithProgress.map(b => minified ? b.book.toJSONMinified() : b.book.toJSONExpanded()).slice(0, limit)
},
- getBooksMostRecentlyAdded(books, limit) {
+ getBooksMostRecentlyAdded(books, limit, minified = false) {
var booksSortedByAddedAt = sort(books).desc(book => book.addedAt)
- return booksSortedByAddedAt.map(b => b.toJSONExpanded()).slice(0, limit)
+ return booksSortedByAddedAt.map(b => minified ? b.toJSONMinified() : b.toJSONExpanded()).slice(0, limit)
},
- getBooksMostRecentlyFinished(booksWithUserAb, limit) {
+ getBooksMostRecentlyFinished(booksWithUserAb, limit, minified = false) {
var booksRead = booksWithUserAb.filter((data) => data.userAudiobook && data.userAudiobook.isRead)
booksRead.sort((a, b) => {
return b.userAudiobook.finishedAt - a.userAudiobook.finishedAt
})
- return booksRead.map(b => b.book.toJSONExpanded()).slice(0, limit)
+ return booksRead.map(b => minified ? b.book.toJSONMinified() : b.book.toJSONExpanded()).slice(0, limit)
},
getSeriesMostRecentlyAdded(series, limit) {
diff --git a/server/utils/prober.js b/server/utils/prober.js
index 974eca07..d8b48827 100644
--- a/server/utils/prober.js
+++ b/server/utils/prober.js
@@ -98,6 +98,7 @@ function parseMediaStreamInfo(stream, all_streams, total_bit_rate) {
language: tryGrabTag(stream, 'language'),
title: tryGrabTag(stream, 'title')
}
+ if (stream.tags) info.tags = stream.tags
if (info.type === 'audio' || info.type === 'subtitle') {
var disposition = stream.disposition || {}
@@ -188,6 +189,14 @@ function parseTags(format, verbose) {
return tags
}
+function getDefaultAudioStream(audioStreams) {
+ if (!audioStreams || !audioStreams.length) return null
+ if (audioStreams.length === 1) return audioStreams[0]
+ var defaultStream = audioStreams.find(a => a.is_default)
+ if (!defaultStream) return audioStreams[0]
+ return defaultStream
+}
+
function parseProbeData(data, verbose = false) {
try {
var { format, streams, chapters } = data
@@ -212,17 +221,26 @@ function parseProbeData(data, verbose = false) {
const cleaned_streams = streams.map(s => parseMediaStreamInfo(s, streams, cleanedData.bit_rate))
cleanedData.video_stream = cleaned_streams.find(s => s.type === 'video')
- cleanedData.audio_streams = cleaned_streams.filter(s => s.type === 'audio')
- cleanedData.subtitle_streams = cleaned_streams.filter(s => s.type === 'subtitle')
+ var audioStreams = cleaned_streams.filter(s => s.type === 'audio')
+ cleanedData.audio_stream = getDefaultAudioStream(audioStreams)
- if (cleanedData.audio_streams.length && cleanedData.video_stream) {
+ if (cleanedData.audio_stream && cleanedData.video_stream) {
var videoBitrate = cleanedData.video_stream.bit_rate
// If audio stream bitrate larger then video, most likely incorrect
- if (cleanedData.audio_streams.find(astream => astream.bit_rate > videoBitrate)) {
+ if (cleanedData.audio_stream.bit_rate > videoBitrate) {
cleanedData.video_stream.bit_rate = cleanedData.bit_rate
}
}
+ // If format does not have tags, check audio stream (https://github.com/advplyr/audiobookshelf/issues/256)
+ if (!format.tags && cleanedData.audio_stream && cleanedData.audio_stream.tags) {
+ var tags = parseTags(cleanedData.audio_stream)
+ cleanedData = {
+ ...cleanedData,
+ ...tags
+ }
+ }
+
cleanedData.chapters = parseChapters(chapters)
return cleanedData
@@ -232,22 +250,8 @@ function parseProbeData(data, verbose = false) {
}
}
-function probe(filepath, verbose = false) {
- return new Promise((resolve) => {
- Ffmpeg.ffprobe(filepath, ['-show_chapters'], (err, raw) => {
- if (err) {
- console.error(err)
- resolve(null)
- } else {
- resolve(parseProbeData(raw, verbose))
- }
- })
- })
-}
-module.exports.probe = probe
-
// Updated probe returns AudioProbeData object
-function probe2(filepath, verbose = false) {
+function probe(filepath, verbose = false) {
return new Promise((resolve) => {
Ffmpeg.ffprobe(filepath, ['-show_chapters'], (err, raw) => {
if (err) {
@@ -258,7 +262,7 @@ function probe2(filepath, verbose = false) {
})
} else {
var rawProbeData = parseProbeData(raw, verbose)
- if (!rawProbeData || !rawProbeData.audio_streams.length) {
+ if (!rawProbeData || !rawProbeData.audio_stream) {
resolve({
error: rawProbeData ? 'Invalid audio file: no audio streams found' : 'Probe Failed'
})
@@ -271,4 +275,4 @@ function probe2(filepath, verbose = false) {
})
})
}
-module.exports.probe2 = probe2
\ No newline at end of file
+module.exports.probe = probe
\ No newline at end of file