mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-07-19 02:14:31 +02:00
Update scanner, music meta tags and fix issue with force update
This commit is contained in:
parent
5410aae8fc
commit
9de7be1cb4
11 changed files with 388 additions and 110 deletions
|
@ -131,7 +131,7 @@ class AudioFile {
|
|||
this.channels = probeData.channels
|
||||
this.channelLayout = probeData.channelLayout
|
||||
this.chapters = probeData.chapters || []
|
||||
this.metaTags = probeData.audioFileMetadata
|
||||
this.metaTags = probeData.audioMetaTags
|
||||
this.embeddedCoverArt = probeData.embeddedCoverArt
|
||||
}
|
||||
|
||||
|
@ -167,9 +167,7 @@ class AudioFile {
|
|||
let hasUpdated = false
|
||||
|
||||
const newjson = scannedAudioFile.toJSON()
|
||||
if (this.manuallyVerified) newjson.manuallyVerified = true
|
||||
if (this.exclude) newjson.exclude = true
|
||||
newjson.addedAt = this.addedAt
|
||||
const ignoreKeys = ['manuallyVerified', 'exclude', 'addedAt', 'updatedAt']
|
||||
|
||||
for (const key in newjson) {
|
||||
if (key === 'metadata') {
|
||||
|
@ -185,7 +183,7 @@ class AudioFile {
|
|||
if (this.syncChapters(newjson.chapters || [])) {
|
||||
hasUpdated = true
|
||||
}
|
||||
} else if (this[key] !== newjson[key]) {
|
||||
} else if (!ignoreKeys.includes(key) && this[key] !== newjson[key]) {
|
||||
this[key] = newjson[key]
|
||||
hasUpdated = true
|
||||
}
|
||||
|
|
|
@ -131,6 +131,12 @@ class Music {
|
|||
this.audioFile = audioFile
|
||||
}
|
||||
|
||||
setMetadataFromAudioFile(overrideExistingDetails = false) {
|
||||
if (!this.audioFile) return false
|
||||
if (!this.audioFile.metaTags) return false
|
||||
return this.metadata.setDataFromAudioMetaTags(this.audioFile.metaTags, overrideExistingDetails)
|
||||
}
|
||||
|
||||
syncMetadataFiles(textMetadataFiles, opfMetadataOverrideDetails) {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -21,6 +21,15 @@ class AudioMetaTags {
|
|||
this.tagLanguage = null
|
||||
this.tagASIN = null
|
||||
this.tagOverdriveMediaMarker = null
|
||||
this.tagOriginalYear = null
|
||||
this.tagReleaseCountry = null
|
||||
this.tagReleaseType = null
|
||||
this.tagReleaseStatus = null
|
||||
this.tagISRC = null
|
||||
this.tagMusicBrainzTrackId = null
|
||||
this.tagMusicBrainzAlbumId = null
|
||||
this.tagMusicBrainzAlbumArtistId = null
|
||||
this.tagMusicBrainzArtistId = null
|
||||
|
||||
if (metadata) {
|
||||
this.construct(metadata)
|
||||
|
@ -29,7 +38,7 @@ class AudioMetaTags {
|
|||
|
||||
toJSON() {
|
||||
// Only return the tags that are actually set
|
||||
var json = {}
|
||||
const json = {}
|
||||
for (const key in this) {
|
||||
if (key.startsWith('tag') && this[key]) {
|
||||
json[key] = this[key]
|
||||
|
@ -38,6 +47,51 @@ class AudioMetaTags {
|
|||
return json
|
||||
}
|
||||
|
||||
get trackNumAndTotal() {
|
||||
const data = {
|
||||
number: null,
|
||||
total: null
|
||||
}
|
||||
|
||||
// Track ID3 tag might be "3/10" or just "3"
|
||||
if (this.tagTrack) {
|
||||
const trackParts = this.tagTrack.split('/').map(part => Number(part))
|
||||
if (trackParts.length > 0) {
|
||||
// Fractional track numbers not supported
|
||||
data.number = !isNaN(trackParts[0]) ? Math.trunc(trackParts[0]) : null
|
||||
}
|
||||
if (trackParts.length > 1) {
|
||||
data.total = !isNaN(trackParts[1]) ? trackParts[1] : null
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
get discNumAndTotal() {
|
||||
const data = {
|
||||
number: null,
|
||||
total: null
|
||||
}
|
||||
|
||||
if (this.tagDisc) {
|
||||
const discParts = this.tagDisc.split('/').map(p => Number(p))
|
||||
if (discParts.length > 0) {
|
||||
data.number = !isNaN(discParts[0]) ? Math.trunc(discParts[0]) : null
|
||||
}
|
||||
if (discParts.length > 1) {
|
||||
data.total = !isNaN(discParts[1]) ? discParts[1] : null
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
get discNumber() { return this.discNumAndTotal.number }
|
||||
get discTotal() { return this.discNumAndTotal.total }
|
||||
get trackNumber() { return this.trackNumAndTotal.number }
|
||||
get trackTotal() { return this.trackNumAndTotal.total }
|
||||
|
||||
construct(metadata) {
|
||||
this.tagAlbum = metadata.tagAlbum || null
|
||||
this.tagArtist = metadata.tagArtist || null
|
||||
|
@ -60,6 +114,15 @@ class AudioMetaTags {
|
|||
this.tagLanguage = metadata.tagLanguage || null
|
||||
this.tagASIN = metadata.tagASIN || null
|
||||
this.tagOverdriveMediaMarker = metadata.tagOverdriveMediaMarker || null
|
||||
this.tagOriginalYear = metadata.tagOriginalYear || null
|
||||
this.tagReleaseCountry = metadata.tagReleaseCountry || null
|
||||
this.tagReleaseType = metadata.tagReleaseType || null
|
||||
this.tagReleaseStatus = metadata.tagReleaseStatus || null
|
||||
this.tagISRC = metadata.tagISRC || null
|
||||
this.tagMusicBrainzTrackId = metadata.tagMusicBrainzTrackId || null
|
||||
this.tagMusicBrainzAlbumId = metadata.tagMusicBrainzAlbumId || null
|
||||
this.tagMusicBrainzAlbumArtistId = metadata.tagMusicBrainzAlbumArtistId || null
|
||||
this.tagMusicBrainzArtistId = metadata.tagMusicBrainzArtistId || null
|
||||
}
|
||||
|
||||
// Data parsed in prober.js
|
||||
|
@ -85,6 +148,15 @@ class AudioMetaTags {
|
|||
this.tagLanguage = payload.file_tag_language || null
|
||||
this.tagASIN = payload.file_tag_asin || null
|
||||
this.tagOverdriveMediaMarker = payload.file_tag_overdrive_media_marker || null
|
||||
this.tagOriginalYear = payload.file_tag_originalyear || null
|
||||
this.tagReleaseCountry = payload.file_tag_releasecountry || null
|
||||
this.tagReleaseType = payload.file_tag_releasetype || null
|
||||
this.tagReleaseStatus = payload.file_tag_releasestatus || null
|
||||
this.tagISRC = payload.file_tag_isrc || null
|
||||
this.tagMusicBrainzTrackId = payload.file_tag_musicbrainz_trackid || null
|
||||
this.tagMusicBrainzAlbumId = payload.file_tag_musicbrainz_albumid || null
|
||||
this.tagMusicBrainzAlbumArtistId = payload.file_tag_musicbrainz_albumartistid || null
|
||||
this.tagMusicBrainzArtistId = payload.file_tag_musicbrainz_artistid || null
|
||||
}
|
||||
|
||||
setDataFromTone(tags) {
|
||||
|
@ -114,9 +186,18 @@ class AudioMetaTags {
|
|||
tagLanguage: payload.file_tag_language || null,
|
||||
tagASIN: payload.file_tag_asin || null,
|
||||
tagOverdriveMediaMarker: payload.file_tag_overdrive_media_marker || null,
|
||||
tagOriginalYear: payload.file_tag_originalyear || null,
|
||||
tagReleaseCountry: payload.file_tag_releasecountry || null,
|
||||
tagReleaseType: payload.file_tag_releasetype || null,
|
||||
tagReleaseStatus: payload.file_tag_releasestatus || null,
|
||||
tagISRC: payload.file_tag_isrc || null,
|
||||
tagMusicBrainzTrackId: payload.file_tag_musicbrainz_trackid || null,
|
||||
tagMusicBrainzAlbumId: payload.file_tag_musicbrainz_albumid || null,
|
||||
tagMusicBrainzAlbumArtistId: payload.file_tag_musicbrainz_albumartistid || null,
|
||||
tagMusicBrainzArtistId: payload.file_tag_musicbrainz_artistid || null
|
||||
}
|
||||
|
||||
var hasUpdates = false
|
||||
let hasUpdates = false
|
||||
for (const key in dataMap) {
|
||||
if (dataMap[key] !== this[key]) {
|
||||
this[key] = dataMap[key]
|
||||
|
|
|
@ -306,11 +306,9 @@ class BookMetadata {
|
|||
// tagToUse = mapping.altTag
|
||||
}
|
||||
|
||||
if (value && typeof value === 'string') { // Trim whitespace
|
||||
value = value.trim()
|
||||
}
|
||||
if (value && typeof value === 'string') {
|
||||
value = value.trim() // Trim whitespace
|
||||
|
||||
if (value) {
|
||||
if (mapping.key === 'narrators' && (!this.narrators.length || overrideExistingDetails)) {
|
||||
updatePayload.narrators = this.parseNarratorsTag(value)
|
||||
} else if (mapping.key === 'authors' && (!this.authors.length || overrideExistingDetails)) {
|
||||
|
@ -335,13 +333,13 @@ class BookMetadata {
|
|||
|
||||
// Returns array of names in First Last format
|
||||
parseNarratorsTag(narratorsTag) {
|
||||
var parsed = parseNameString.parse(narratorsTag)
|
||||
const parsed = parseNameString.parse(narratorsTag)
|
||||
return parsed ? parsed.names : []
|
||||
}
|
||||
|
||||
// Return array of authors minified with placeholder id
|
||||
parseAuthorsTag(authorsTag) {
|
||||
var parsed = parseNameString.parse(authorsTag)
|
||||
const parsed = parseNameString.parse(authorsTag)
|
||||
if (!parsed) return []
|
||||
return (parsed.names || []).map((au) => {
|
||||
return {
|
||||
|
@ -353,7 +351,7 @@ class BookMetadata {
|
|||
|
||||
parseGenresTag(genreTag) {
|
||||
if (!genreTag || !genreTag.length) return []
|
||||
var separators = ['/', '//', ';']
|
||||
const separators = ['/', '//', ';']
|
||||
for (let i = 0; i < separators.length; i++) {
|
||||
if (genreTag.includes(separators[i])) {
|
||||
return genreTag.split(separators[i]).map(genre => genre.trim()).filter(g => !!g)
|
||||
|
|
|
@ -4,13 +4,31 @@ const { areEquivalent, copyValue, cleanStringForSearch, getTitleIgnorePrefix, ge
|
|||
class MusicMetadata {
|
||||
constructor(metadata) {
|
||||
this.title = null
|
||||
this.artist = null
|
||||
this.artists = [] // Array of strings
|
||||
this.album = null
|
||||
this.albumArtist = null
|
||||
this.genres = [] // Array of strings
|
||||
this.composer = null
|
||||
this.originalYear = null
|
||||
this.releaseDate = null
|
||||
this.releaseCountry = null
|
||||
this.releaseType = null
|
||||
this.releaseStatus = null
|
||||
this.recordLabel = null
|
||||
this.language = null
|
||||
this.explicit = false
|
||||
|
||||
this.discNumber = null
|
||||
this.discTotal = null
|
||||
this.trackNumber = null
|
||||
this.trackTotal = null
|
||||
|
||||
this.isrc = null
|
||||
this.musicBrainzTrackId = null
|
||||
this.musicBrainzAlbumId = null
|
||||
this.musicBrainzAlbumArtistId = null
|
||||
this.musicBrainzArtistId = null
|
||||
|
||||
if (metadata) {
|
||||
this.construct(metadata)
|
||||
}
|
||||
|
@ -18,23 +36,55 @@ class MusicMetadata {
|
|||
|
||||
construct(metadata) {
|
||||
this.title = metadata.title
|
||||
this.artist = metadata.artist
|
||||
this.artists = metadata.artists ? [...metadata.artists] : []
|
||||
this.album = metadata.album
|
||||
this.albumArtist = metadata.albumArtist
|
||||
this.genres = metadata.genres ? [...metadata.genres] : []
|
||||
this.composer = metadata.composer || null
|
||||
this.originalYear = metadata.originalYear || null
|
||||
this.releaseDate = metadata.releaseDate || null
|
||||
this.language = metadata.language
|
||||
this.releaseCountry = metadata.releaseCountry || null
|
||||
this.releaseType = metadata.releaseType || null
|
||||
this.releaseStatus = metadata.releaseStatus || null
|
||||
this.recordLabel = metadata.recordLabel || null
|
||||
this.language = metadata.language || null
|
||||
this.explicit = !!metadata.explicit
|
||||
this.discNumber = metadata.discNumber || null
|
||||
this.discTotal = metadata.discTotal || null
|
||||
this.trackNumber = metadata.trackNumber || null
|
||||
this.trackTotal = metadata.trackTotal || null
|
||||
this.isrc = metadata.isrc || null
|
||||
this.musicBrainzTrackId = metadata.musicBrainzTrackId || null
|
||||
this.musicBrainzAlbumId = metadata.musicBrainzAlbumId || null
|
||||
this.musicBrainzAlbumArtistId = metadata.musicBrainzAlbumArtistId || null
|
||||
this.musicBrainzArtistId = metadata.musicBrainzArtistId || null
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
title: this.title,
|
||||
artist: this.artist,
|
||||
artists: [...this.artists],
|
||||
album: this.album,
|
||||
albumArtist: this.albumArtist,
|
||||
genres: [...this.genres],
|
||||
composer: this.composer,
|
||||
originalYear: this.originalYear,
|
||||
releaseDate: this.releaseDate,
|
||||
releaseCountry: this.releaseCountry,
|
||||
releaseType: this.releaseType,
|
||||
releaseStatus: this.releaseStatus,
|
||||
recordLabel: this.recordLabel,
|
||||
language: this.language,
|
||||
explicit: this.explicit
|
||||
explicit: this.explicit,
|
||||
discNumber: this.discNumber,
|
||||
discTotal: this.discTotal,
|
||||
trackNumber: this.trackNumber,
|
||||
trackTotal: this.trackTotal,
|
||||
isrc: this.isrc,
|
||||
musicBrainzTrackId: this.musicBrainzTrackId,
|
||||
musicBrainzAlbumId: this.musicBrainzAlbumId,
|
||||
musicBrainzAlbumArtistId: this.musicBrainzAlbumArtistId,
|
||||
musicBrainzArtistId: this.musicBrainzArtistId
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,12 +92,28 @@ class MusicMetadata {
|
|||
return {
|
||||
title: this.title,
|
||||
titleIgnorePrefix: this.titlePrefixAtEnd,
|
||||
artist: this.artist,
|
||||
artists: [...this.artists],
|
||||
album: this.album,
|
||||
albumArtist: this.albumArtist,
|
||||
genres: [...this.genres],
|
||||
composer: this.composer,
|
||||
originalYear: this.originalYear,
|
||||
releaseDate: this.releaseDate,
|
||||
releaseCountry: this.releaseCountry,
|
||||
releaseType: this.releaseType,
|
||||
releaseStatus: this.releaseStatus,
|
||||
recordLabel: this.recordLabel,
|
||||
language: this.language,
|
||||
explicit: this.explicit
|
||||
explicit: this.explicit,
|
||||
discNumber: this.discNumber,
|
||||
discTotal: this.discTotal,
|
||||
trackNumber: this.trackNumber,
|
||||
trackTotal: this.trackTotal,
|
||||
isrc: this.isrc,
|
||||
musicBrainzTrackId: this.musicBrainzTrackId,
|
||||
musicBrainzAlbumId: this.musicBrainzAlbumId,
|
||||
musicBrainzAlbumArtistId: this.musicBrainzAlbumArtistId,
|
||||
musicBrainzArtistId: this.musicBrainzArtistId
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,7 +134,7 @@ class MusicMetadata {
|
|||
}
|
||||
|
||||
searchQuery(query) { // Returns key if match is found
|
||||
const keysToCheck = ['title', 'artist', 'album']
|
||||
const keysToCheck = ['title', 'album']
|
||||
for (const key of keysToCheck) {
|
||||
if (this[key] && cleanStringForSearch(String(this[key])).includes(query)) {
|
||||
return {
|
||||
|
@ -100,5 +166,154 @@ class MusicMetadata {
|
|||
}
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
parseArtistsTag(artistsTag) {
|
||||
if (!artistsTag || !artistsTag.length) return []
|
||||
const separators = ['/', '//', ';']
|
||||
for (let i = 0; i < separators.length; i++) {
|
||||
if (artistsTag.includes(separators[i])) {
|
||||
return artistsTag.split(separators[i]).map(artist => artist.trim()).filter(a => !!a)
|
||||
}
|
||||
}
|
||||
return [artistsTag]
|
||||
}
|
||||
|
||||
parseGenresTag(genreTag) {
|
||||
if (!genreTag || !genreTag.length) return []
|
||||
const separators = ['/', '//', ';']
|
||||
for (let i = 0; i < separators.length; i++) {
|
||||
if (genreTag.includes(separators[i])) {
|
||||
return genreTag.split(separators[i]).map(genre => genre.trim()).filter(g => !!g)
|
||||
}
|
||||
}
|
||||
return [genreTag]
|
||||
}
|
||||
|
||||
setDataFromAudioMetaTags(audioFileMetaTags, overrideExistingDetails = false) {
|
||||
const MetadataMapArray = [
|
||||
{
|
||||
tag: 'tagTitle',
|
||||
key: 'title',
|
||||
},
|
||||
{
|
||||
tag: 'tagArtist',
|
||||
key: 'artists'
|
||||
},
|
||||
{
|
||||
tag: 'tagAlbumArtist',
|
||||
key: 'albumArtist'
|
||||
},
|
||||
{
|
||||
tag: 'tagAlbum',
|
||||
key: 'album',
|
||||
},
|
||||
{
|
||||
tag: 'tagPublisher',
|
||||
key: 'recordLabel'
|
||||
},
|
||||
{
|
||||
tag: 'tagComposer',
|
||||
key: 'composer'
|
||||
},
|
||||
{
|
||||
tag: 'tagDate',
|
||||
key: 'releaseDate'
|
||||
},
|
||||
{
|
||||
tag: 'tagReleaseCountry',
|
||||
key: 'releaseCountry'
|
||||
},
|
||||
{
|
||||
tag: 'tagReleaseType',
|
||||
key: 'releaseType'
|
||||
},
|
||||
{
|
||||
tag: 'tagReleaseStatus',
|
||||
key: 'releaseStatus'
|
||||
},
|
||||
{
|
||||
tag: 'tagOriginalYear',
|
||||
key: 'originalYear'
|
||||
},
|
||||
{
|
||||
tag: 'tagGenre',
|
||||
key: 'genres'
|
||||
},
|
||||
{
|
||||
tag: 'tagLanguage',
|
||||
key: 'language'
|
||||
},
|
||||
{
|
||||
tag: 'tagLanguage',
|
||||
key: 'language'
|
||||
},
|
||||
{
|
||||
tag: 'tagISRC',
|
||||
key: 'isrc'
|
||||
},
|
||||
{
|
||||
tag: 'tagMusicBrainzTrackId',
|
||||
key: 'musicBrainzTrackId'
|
||||
},
|
||||
{
|
||||
tag: 'tagMusicBrainzAlbumId',
|
||||
key: 'musicBrainzAlbumId'
|
||||
},
|
||||
{
|
||||
tag: 'tagMusicBrainzAlbumArtistId',
|
||||
key: 'musicBrainzAlbumArtistId'
|
||||
},
|
||||
{
|
||||
tag: 'tagMusicBrainzArtistId',
|
||||
key: 'musicBrainzArtistId'
|
||||
},
|
||||
{
|
||||
tag: 'trackNumber',
|
||||
key: 'trackNumber'
|
||||
},
|
||||
{
|
||||
tag: 'trackTotal',
|
||||
key: 'trackTotal'
|
||||
},
|
||||
{
|
||||
tag: 'discNumber',
|
||||
key: 'discNumber'
|
||||
},
|
||||
{
|
||||
tag: 'discTotal',
|
||||
key: 'discTotal'
|
||||
}
|
||||
]
|
||||
|
||||
const updatePayload = {}
|
||||
|
||||
// Metadata is only mapped to the music track if it is empty
|
||||
MetadataMapArray.forEach((mapping) => {
|
||||
let value = audioFileMetaTags[mapping.tag]
|
||||
// let tagToUse = mapping.tag
|
||||
if (!value && mapping.altTag) {
|
||||
value = audioFileMetaTags[mapping.altTag]
|
||||
// tagToUse = mapping.altTag
|
||||
}
|
||||
|
||||
if (value && typeof value === 'string') {
|
||||
value = value.trim() // Trim whitespace
|
||||
|
||||
if (mapping.key === 'artists' && (!this.artists.length || overrideExistingDetails)) {
|
||||
updatePayload.artists = this.parseArtistsTag(value)
|
||||
} else if (mapping.key === 'genres' && (!this.genres.length || overrideExistingDetails)) {
|
||||
updatePayload.genres = this.parseGenresTag(value)
|
||||
} else if (!this[mapping.key] || overrideExistingDetails) {
|
||||
updatePayload[mapping.key] = value
|
||||
// Logger.debug(`[Book] Mapping metadata to key ${tagToUse} => ${mapping.key}: ${updatePayload[mapping.key]}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (Object.keys(updatePayload).length) {
|
||||
return this.update(updatePayload)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
module.exports = MusicMetadata
|
Loading…
Add table
Add a link
Reference in a new issue