audiobookshelf/server/objects/metadata/MusicMetadata.js
2023-01-06 17:39:15 -06:00

320 lines
8.8 KiB
JavaScript

const Logger = require('../../Logger')
const { areEquivalent, copyValue, cleanStringForSearch, getTitleIgnorePrefix, getTitlePrefixAtEnd } = require('../../utils/index')
class MusicMetadata {
constructor(metadata) {
this.title = 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)
}
}
construct(metadata) {
this.title = metadata.title
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.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,
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,
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
}
}
toJSONMinified() {
return {
title: this.title,
titleIgnorePrefix: this.titlePrefixAtEnd,
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,
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
}
}
toJSONExpanded() {
return this.toJSONMinified()
}
clone() {
return new MusicMetadata(this.toJSON())
}
get titleIgnorePrefix() {
return getTitleIgnorePrefix(this.title)
}
get titlePrefixAtEnd() {
return getTitlePrefixAtEnd(this.title)
}
searchQuery(query) { // Returns key if match is found
const keysToCheck = ['title', 'album']
for (const key of keysToCheck) {
if (this[key] && cleanStringForSearch(String(this[key])).includes(query)) {
return {
matchKey: key,
matchText: this[key]
}
}
}
return null
}
setData(mediaMetadata = {}) {
this.title = mediaMetadata.title || null
this.artist = mediaMetadata.artist || null
this.album = mediaMetadata.album || null
}
update(payload) {
const json = this.toJSON()
let hasUpdates = false
for (const key in json) {
if (payload[key] !== undefined) {
if (!areEquivalent(payload[key], json[key])) {
this[key] = copyValue(payload[key])
Logger.debug('[MusicMetadata] Key updated', key, this[key])
hasUpdates = true
}
}
}
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' || typeof value === 'number')) {
value = value.toString().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