diff --git a/client/components/cards/AudiobookSearchCard.vue b/client/components/cards/AudiobookSearchCard.vue
index 9edbb9cd..a7de5c21 100644
--- a/client/components/cards/AudiobookSearchCard.vue
+++ b/client/components/cards/AudiobookSearchCard.vue
@@ -7,7 +7,7 @@
{{ matchHtml }}
- by {{ author }}
+ by {{ authorFL }}
@@ -39,8 +39,8 @@ export default {
subtitle() {
return this.book ? this.book.subtitle : ''
},
- author() {
- return this.book ? this.book.author : 'Unknown'
+ authorFL() {
+ return this.book ? this.book.authorFL : 'Unknown'
},
matchHtml() {
if (!this.matchText || !this.search) return ''
@@ -62,7 +62,7 @@ export default {
html += lastPart
if (this.matchKey === 'tags') return `Tags: ${html}
`
- if (this.matchKey === 'author') return `by ${html}`
+ if (this.matchKey === 'authorFL') return `by ${html}`
if (this.matchKey === 'series') return `Series: ${html}
`
return `${html}`
}
diff --git a/client/components/modals/edit-tabs/Cover.vue b/client/components/modals/edit-tabs/Cover.vue
index 4241ea19..22dc340f 100644
--- a/client/components/modals/edit-tabs/Cover.vue
+++ b/client/components/modals/edit-tabs/Cover.vue
@@ -188,13 +188,13 @@ export default {
},
init() {
this.showLocalCovers = false
- if (this.coversFound.length && (this.searchTitle !== this.book.title || this.searchAuthor !== this.book.author)) {
+ if (this.coversFound.length && (this.searchTitle !== this.book.title || this.searchAuthor !== this.book.authorFL)) {
this.coversFound = []
this.hasSearched = false
}
this.imageUrl = this.book.cover || ''
this.searchTitle = this.book.title || ''
- this.searchAuthor = this.book.author || ''
+ this.searchAuthor = this.book.authorFL || ''
},
removeCover() {
if (!this.book.cover) {
diff --git a/client/pages/audiobook/_id/edit.vue b/client/pages/audiobook/_id/edit.vue
index d6a77f92..d2bf15db 100644
--- a/client/pages/audiobook/_id/edit.vue
+++ b/client/pages/audiobook/_id/edit.vue
@@ -16,13 +16,18 @@
Track From Filename
- {{ currentSort === 'filename' ? 'expand_more' : 'unfold_more' }}
+ {{ currentSort === 'track-filename' ? 'expand_more' : 'unfold_more' }}
Track From Metadata
{{ currentSort === 'metadata' ? 'expand_more' : 'unfold_more' }}
- Filename
+ CD From Filename
+
+ Filename
+ {{ currentSort === 'filename' ? 'expand_more' : 'unfold_more' }}
+
+
Size
Duration
@@ -43,6 +48,9 @@
{{ audio.trackNumFromMeta }}
+
+ {{ audio.cdNumFromFilename }}
+
{{ audio.filename }}
@@ -236,6 +244,12 @@ export default {
if (a.trackNumFromFilename === null) return 1
return a.trackNumFromFilename - b.trackNumFromFilename
})
+ this.currentSort = 'track-filename'
+ },
+ sortByFilename() {
+ this.files.sort((a, b) => {
+ return (a.filename || '').toLowerCase().localeCompare((b.filename || '').toLowerCase())
+ })
this.currentSort = 'filename'
},
checkTrackNumbers() {
diff --git a/client/pages/audiobook/_id/index.vue b/client/pages/audiobook/_id/index.vue
index 051dc90e..6ac5f69b 100644
--- a/client/pages/audiobook/_id/index.vue
+++ b/client/pages/audiobook/_id/index.vue
@@ -19,7 +19,7 @@
- by {{ author }}{{ authorFL }}Unknown
{{ seriesText }}
diff --git a/client/store/audiobooks.js b/client/store/audiobooks.js
index 94b9f43b..b27fb24b 100644
--- a/client/store/audiobooks.js
+++ b/client/store/audiobooks.js
@@ -54,7 +54,7 @@ export const getters = {
if (filter === 'No Series') filtered = filtered.filter(ab => ab.book && !ab.book.series)
else filtered = filtered.filter(ab => ab.book && ab.book.series === filter)
}
- else if (group === 'authors') filtered = filtered.filter(ab => ab.book && ab.book.author === filter)
+ else if (group === 'authors') filtered = filtered.filter(ab => ab.book && ab.book.authorFL === filter)
else if (group === 'narrators') filtered = filtered.filter(ab => ab.book && ab.book.narrator === filter)
else if (group === 'progress') {
filtered = filtered.filter(ab => {
@@ -122,7 +122,7 @@ export const getters = {
return seriesArray
},
getUniqueAuthors: (state) => {
- var _authors = state.audiobooks.filter(ab => !!(ab.book && ab.book.author)).map(ab => ab.book.author)
+ var _authors = state.audiobooks.filter(ab => !!(ab.book && ab.book.authorFL)).map(ab => ab.book.authorFL)
return [...new Set(_authors)].sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
},
getUniqueNarrators: (state) => {
diff --git a/server/Scanner.js b/server/Scanner.js
index 7d15d4a2..2322e252 100644
--- a/server/Scanner.js
+++ b/server/Scanner.js
@@ -109,7 +109,7 @@ class Scanner {
titleDistance: 2,
authorDistance: 2
}
- var results = await this.bookFinder.findCovers('openlibrary', audiobook.title, audiobook.author, options)
+ var results = await this.bookFinder.findCovers('openlibrary', audiobook.title, audiobook.authorFL, options)
if (results.length) {
Logger.debug(`[Scanner] Found best cover for "${audiobook.title}"`)
diff --git a/server/objects/AudioFile.js b/server/objects/AudioFile.js
index c9248e46..cf873e42 100644
--- a/server/objects/AudioFile.js
+++ b/server/objects/AudioFile.js
@@ -13,6 +13,7 @@ class AudioFile {
this.trackNumFromMeta = null
this.trackNumFromFilename = null
+ this.cdNumFromFilename = null
this.format = null
this.duration = null
@@ -39,6 +40,16 @@ class AudioFile {
}
}
+ // Sort number takes cd num into account
+ // get sortNumber() {
+ // if (this.manuallyVerified) return this.index
+ // var num = this.index
+ // if (this.cdNumFromFilename && !isNaN(this.cdNumFromFilename)) {
+ // num += (Number(this.cdNumFromFilename) * 1000)
+ // }
+ // return num
+ // }
+
toJSON() {
return {
index: this.index,
@@ -50,6 +61,7 @@ class AudioFile {
addedAt: this.addedAt,
trackNumFromMeta: this.trackNumFromMeta,
trackNumFromFilename: this.trackNumFromFilename,
+ cdNumFromFilename: this.cdNumFromFilename,
manuallyVerified: !!this.manuallyVerified,
invalid: !!this.invalid,
exclude: !!this.exclude,
@@ -84,6 +96,7 @@ class AudioFile {
this.trackNumFromMeta = data.trackNumFromMeta || null
this.trackNumFromFilename = data.trackNumFromFilename || null
+ this.cdNumFromFilename = data.cdNumFromFilename || null
this.format = data.format
this.duration = data.duration
@@ -117,6 +130,7 @@ class AudioFile {
this.trackNumFromMeta = data.trackNumFromMeta || null
this.trackNumFromFilename = data.trackNumFromFilename || null
+ this.cdNumFromFilename = data.cdNumFromFilename || null
this.manuallyVerified = !!data.manuallyVerified
this.invalid = !!data.invalid
@@ -178,7 +192,10 @@ class AudioFile {
channels: data.channels,
channelLayout: data.channel_layout,
chapters: data.chapters || [],
- embeddedCoverArt: data.embedded_cover_art || null
+ embeddedCoverArt: data.embedded_cover_art || null,
+ trackNumFromMeta: data.trackNumFromMeta,
+ trackNumFromFilename: data.trackNumFromFilename,
+ cdNumFromFilename: data.cdNumFromFilename
}
var hasUpdates = false
diff --git a/server/objects/Book.js b/server/objects/Book.js
index 295682b4..30539b10 100644
--- a/server/objects/Book.js
+++ b/server/objects/Book.js
@@ -35,11 +35,11 @@ class Book {
get _title() { return this.title || '' }
get _subtitle() { return this.subtitle || '' }
get _narrator() { return this.narrator || '' }
- get _author() { return this.author || '' }
+ get _author() { return this.authorFL || '' }
get _series() { return this.series || '' }
get shouldSearchForCover() {
- if (this.author !== this.lastCoverSearchAuthor || this.title !== this.lastCoverSearchTitle || !this.lastCoverSearch) return true
+ if (this.authorFL !== this.lastCoverSearchAuthor || this.title !== this.lastCoverSearchTitle || !this.lastCoverSearch) return true
var timeSinceLastSearch = Date.now() - this.lastCoverSearch
return timeSinceLastSearch > 1000 * 60 * 60 * 24 * 7 // every 7 days do another lookup
}
@@ -180,7 +180,7 @@ class Book {
updateLastCoverSearch(coverWasFound) {
this.lastCoverSearch = coverWasFound ? null : Date.now()
- this.lastCoverSearchAuthor = coverWasFound ? null : this.author
+ this.lastCoverSearchAuthor = coverWasFound ? null : this.authorFL
this.lastCoverSearchTitle = coverWasFound ? null : this.title
}
@@ -225,7 +225,7 @@ class Book {
var authorMatch = this._author.toLowerCase().includes(search)
var seriesMatch = this._series.toLowerCase().includes(search)
- var bookMatchKey = titleMatch ? 'title' : subtitleMatch ? 'subtitle' : authorMatch ? 'author' : seriesMatch ? 'series' : false
+ var bookMatchKey = titleMatch ? 'title' : subtitleMatch ? 'subtitle' : authorMatch ? 'authorFL' : seriesMatch ? 'series' : false
var bookMatchText = bookMatchKey ? this[bookMatchKey] : ''
return {
book: bookMatchKey,
diff --git a/server/utils/audioFileScanner.js b/server/utils/audioFileScanner.js
index 19e358d5..b7322de3 100644
--- a/server/utils/audioFileScanner.js
+++ b/server/utils/audioFileScanner.js
@@ -89,7 +89,10 @@ function getTrackNumberFromFilename(title, author, series, publishYear, filename
if (publishYear) partbasename = partbasename.replace(publishYear)
// Remove eg. "disc 1" from path
- partbasename = partbasename.replace(/ disc \d\d? /i, '')
+ 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
@@ -98,6 +101,27 @@ function getTrackNumberFromFilename(title, author, series, publishYear, filename
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)
@@ -123,6 +147,14 @@ async function scanAudioFiles(audiobook, newAudioFiles) {
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,
@@ -131,7 +163,8 @@ async function scanAudioFiles(audiobook, newAudioFiles) {
ext: audioFile.ext,
...scanData,
trackNumFromMeta,
- trackNumFromFilename
+ trackNumFromFilename,
+ cdNumFromFilename
}
var audioFile = audiobook.addAudioFile(audioFileObj)
@@ -172,6 +205,7 @@ async function scanAudioFiles(audiobook, newAudioFiles) {
}
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
@@ -209,7 +243,27 @@ async function rescanAudioFiles(audiobook) {
// audiobook.invalidAudioFiles.push(parts[i])
continue;
}
- var hasUpdates = audioFile.updateMetadata(scanData)
+
+ 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)
diff --git a/server/utils/scandir.js b/server/utils/scandir.js
index a4d421dd..c7f28e60 100644
--- a/server/utils/scandir.js
+++ b/server/utils/scandir.js
@@ -206,9 +206,16 @@ function getAudiobookDataFromDir(folderPath, dir, parseSubtitle = false) {
var publishYear = null
- // If Title is of format 1999 - Title, then use 1999 as publish year
- var publishYearMatch = title.match(/^([0-9]{4}) - (.+)/)
- if (publishYearMatch && publishYearMatch.length > 2) {
+ // OLD regex (not matching parentheses)
+ // var publishYearMatch = title.match(/^([0-9]{4}) - (.+)/)
+
+ // If Title is of format 1999 OR (1999) - Title, then use 1999 as publish year
+ var publishYearMatch = title.match(/^(\(?[0-9]{4}\)?) - (.+)/)
+ if (publishYearMatch && publishYearMatch.length > 2 && publishYearMatch[1]) {
+ // Strip parentheses
+ if (publishYearMatch[1].startsWith('(') && publishYearMatch[1].endsWith(')')) {
+ publishYearMatch[1] = publishYearMatch[1].slice(1, -1)
+ }
if (!isNaN(publishYearMatch[1])) {
publishYear = publishYearMatch[1]
title = publishYearMatch[2]