audiobookshelf/server/objects/files/AudioFile.js

199 lines
5.7 KiB
JavaScript

const { AudioMimeType } = require('../../utils/constants')
const AudioMetaTags = require('../metadata/AudioMetaTags')
const FileMetadata = require('../metadata/FileMetadata')
class AudioFile {
constructor(data) {
this.index = null
this.ino = null
/** @type {FileMetadata} */
this.metadata = null
this.addedAt = null
this.updatedAt = null
this.trackNumFromMeta = null
this.discNumFromMeta = null
this.trackNumFromFilename = null
this.discNumFromFilename = null
this.format = null
this.duration = null
this.bitRate = null
this.language = null
this.codec = null
this.timeBase = null
this.channels = null
this.channelLayout = null
this.chapters = []
this.embeddedCoverArt = null
// Tags scraped from the audio file
/** @type {AudioMetaTags} */
this.metaTags = null
this.manuallyVerified = false
this.exclude = false
this.error = null
if (data) {
this.construct(data)
}
}
toJSON() {
return {
index: this.index,
ino: this.ino,
metadata: this.metadata.toJSON(),
addedAt: this.addedAt,
updatedAt: this.updatedAt,
trackNumFromMeta: this.trackNumFromMeta,
discNumFromMeta: this.discNumFromMeta,
trackNumFromFilename: this.trackNumFromFilename,
discNumFromFilename: this.discNumFromFilename,
manuallyVerified: !!this.manuallyVerified,
exclude: !!this.exclude,
error: this.error || null,
format: this.format,
duration: this.duration,
bitRate: this.bitRate,
language: this.language,
codec: this.codec,
timeBase: this.timeBase,
channels: this.channels,
channelLayout: this.channelLayout,
chapters: this.chapters,
embeddedCoverArt: this.embeddedCoverArt,
metaTags: this.metaTags?.toJSON() || {},
mimeType: this.mimeType
}
}
construct(data) {
this.index = data.index
this.ino = data.ino
this.metadata = new FileMetadata(data.metadata || {})
this.addedAt = data.addedAt
this.updatedAt = data.updatedAt
this.manuallyVerified = !!data.manuallyVerified
this.exclude = !!data.exclude
this.error = data.error || null
this.trackNumFromMeta = data.trackNumFromMeta
this.discNumFromMeta = data.discNumFromMeta
this.trackNumFromFilename = data.trackNumFromFilename
if (data.cdNumFromFilename !== undefined) this.discNumFromFilename = data.cdNumFromFilename // TEMP:Support old var name
else this.discNumFromFilename = data.discNumFromFilename
this.format = data.format
this.duration = data.duration
this.bitRate = data.bitRate
this.language = data.language
this.codec = data.codec || null
this.timeBase = data.timeBase
this.channels = data.channels
this.channelLayout = data.channelLayout
this.chapters = data.chapters
this.embeddedCoverArt = data.embeddedCoverArt || null
this.metaTags = new AudioMetaTags(data.metaTags || {})
}
get mimeType() {
const format = this.metadata.format.toUpperCase()
if (AudioMimeType[format]) {
return AudioMimeType[format]
} else {
return AudioMimeType.MP3
}
}
// New scanner creates AudioFile from AudioFileScanner
setDataFromProbe(libraryFile, probeData) {
this.ino = libraryFile.ino || null
if (libraryFile.metadata instanceof FileMetadata) {
this.metadata = libraryFile.metadata.clone()
} else {
this.metadata = new FileMetadata(libraryFile.metadata)
}
this.addedAt = Date.now()
this.updatedAt = Date.now()
this.format = probeData.format
this.duration = probeData.duration
this.bitRate = probeData.bitRate || null
this.language = probeData.language
this.codec = probeData.codec || null
this.timeBase = probeData.timeBase
this.channels = probeData.channels
this.channelLayout = probeData.channelLayout
this.chapters = probeData.chapters || []
this.metaTags = probeData.audioMetaTags
this.embeddedCoverArt = probeData.embeddedCoverArt
}
syncChapters(updatedChapters) {
if (this.chapters.length !== updatedChapters.length) {
this.chapters = updatedChapters.map(ch => ({ ...ch }))
return true
} else if (updatedChapters.length === 0) {
if (this.chapters.length > 0) {
this.chapters = []
return true
}
return false
}
let hasUpdates = false
for (let i = 0; i < updatedChapters.length; i++) {
if (JSON.stringify(updatedChapters[i]) !== JSON.stringify(this.chapters[i])) {
hasUpdates = true
}
}
if (hasUpdates) {
this.chapters = updatedChapters.map(ch => ({ ...ch }))
}
return hasUpdates
}
clone() {
return new AudioFile(this.toJSON())
}
/**
*
* @param {AudioFile} scannedAudioFile
* @returns {boolean} true if updates were made
*/
updateFromScan(scannedAudioFile) {
let hasUpdated = false
const newjson = scannedAudioFile.toJSON()
const ignoreKeys = ['manuallyVerified', 'ctimeMs', 'addedAt', 'updatedAt']
for (const key in newjson) {
if (key === 'metadata') {
if (this.metadata.update(newjson[key])) {
hasUpdated = true
}
} else if (key === 'metaTags') {
if (!this.metaTags || !this.metaTags.isEqual(scannedAudioFile.metaTags)) {
this.metaTags = scannedAudioFile.metaTags.clone()
hasUpdated = true
}
} else if (key === 'chapters') {
if (this.syncChapters(newjson.chapters || [])) {
hasUpdated = true
}
} else if (!ignoreKeys.includes(key) && this[key] !== newjson[key]) {
this[key] = newjson[key]
hasUpdated = true
}
}
return hasUpdated
}
}
module.exports = AudioFile