Add: track manager sort by filename #128, Add: Support publish year wrapped in parenthesis, Change: Parse out CD Num from filename, Change: Show First Last author everywhere when Last, First is used in folder name, Fix: Re-Scan updates parsed track nums

This commit is contained in:
advplyr 2021-10-23 20:31:48 -05:00
parent b32b99418a
commit 92c2c53c09
10 changed files with 115 additions and 23 deletions

View File

@ -7,7 +7,7 @@
<p v-if="matchKey === 'subtitle'" class="truncate text-xs text-gray-300">{{ matchHtml }}</p>
<p v-if="matchKey !== 'author'" class="text-xs text-gray-200 truncate">by {{ author }}</p>
<p v-if="matchKey !== 'authorFL'" class="text-xs text-gray-200 truncate">by {{ authorFL }}</p>
<p v-else class="truncate text-xs text-gray-200" v-html="matchHtml" />
<div v-if="matchKey === 'series' || matchKey === 'tags'" class="m-0 p-0 truncate" v-html="matchHtml" />
@ -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 `<p class="truncate">Tags: ${html}</p>`
if (this.matchKey === 'author') return `by ${html}`
if (this.matchKey === 'authorFL') return `by ${html}`
if (this.matchKey === 'series') return `<p class="truncate">Series: ${html}</p>`
return `${html}`
}

View File

@ -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) {

View File

@ -16,13 +16,18 @@
</div>
<div class="font-book text-center px-4 w-32 flex items-center cursor-pointer text-white text-opacity-40 hover:text-opacity-100" @click="sortByFilenameTrack" @mousedown.prevent>
<span class="text-white">Track From Filename</span>
<span class="material-icons ml-1" :class="currentSort === 'filename' ? 'text-white text-opacity-100 text-lg' : 'text-sm'">{{ currentSort === 'filename' ? 'expand_more' : 'unfold_more' }}</span>
<span class="material-icons ml-1" :class="currentSort === 'track-filename' ? 'text-white text-opacity-100 text-lg' : 'text-sm'">{{ currentSort === 'track-filename' ? 'expand_more' : 'unfold_more' }}</span>
</div>
<div class="font-book text-center px-4 w-32 flex items-center cursor-pointer text-white text-opacity-40 hover:text-opacity-100" @click="sortByMetadataTrack" @mousedown.prevent>
<span class="text-white">Track From Metadata</span>
<span class="material-icons ml-1" :class="currentSort === 'metadata' ? 'text-white text-opacity-100 text-lg' : 'text-sm'">{{ currentSort === 'metadata' ? 'expand_more' : 'unfold_more' }}</span>
</div>
<div class="font-book truncate px-4 flex-grow">Filename</div>
<div class="font-mono w-20 text-center">CD From Filename</div>
<div class="font-book text-center px-4 flex-grow flex items-center cursor-pointer text-white text-opacity-40 hover:text-opacity-100" @click="sortByFilename" @mousedown.prevent>
<span class="text-white">Filename</span>
<span class="material-icons ml-1" :class="currentSort === 'filename' ? 'text-white text-opacity-100 text-lg' : 'text-sm'">{{ currentSort === 'filename' ? 'expand_more' : 'unfold_more' }}</span>
</div>
<!-- <div class="font-book truncate px-4 flex-grow">Filename</div> -->
<div class="font-mono w-20 text-center">Size</div>
<div class="font-mono w-20 text-center">Duration</div>
@ -43,6 +48,9 @@
<div class="font-book text-center w-32">
{{ audio.trackNumFromMeta }}
</div>
<div class="font-book truncate px-4 w-20">
{{ audio.cdNumFromFilename }}
</div>
<div class="font-book truncate px-4 flex-grow">
{{ audio.filename }}
</div>
@ -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() {

View File

@ -19,7 +19,7 @@
</div>
<p class="mb-2 mt-0.5 text-gray-100 text-xl">
by <nuxt-link v-if="author" :to="`/library/${libraryId}/bookshelf?filter=authors.${$encode(author)}`" class="hover:underline">{{ author }}</nuxt-link
by <nuxt-link v-if="authorFL" :to="`/library/${libraryId}/bookshelf?filter=authors.${$encode(authorFL)}`" class="hover:underline">{{ authorFL }}</nuxt-link
><span v-else>Unknown</span>
</p>
<nuxt-link v-if="series" :to="`/library/${libraryId}/bookshelf/series?series=${$encode(series)}`" class="hover:underline font-sans text-gray-300 text-lg leading-7 mb-4"> {{ seriesText }}</nuxt-link>

View File

@ -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) => {

View File

@ -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}"`)

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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]