mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-27 14:25:34 +02:00
Clean out old unused objects
This commit is contained in:
@@ -1,12 +1,10 @@
|
||||
const uuidv4 = require("uuid").v4
|
||||
const uuidv4 = require('uuid').v4
|
||||
const fs = require('../libs/fsExtra')
|
||||
const Path = require('path')
|
||||
const Logger = require('../Logger')
|
||||
const LibraryFile = require('./files/LibraryFile')
|
||||
const Book = require('./mediaTypes/Book')
|
||||
const Podcast = require('./mediaTypes/Podcast')
|
||||
const Video = require('./mediaTypes/Video')
|
||||
const Music = require('./mediaTypes/Music')
|
||||
const { areEquivalent, copyValue } = require('../utils/index')
|
||||
const { filePathToPOSIX, getFileTimestampsWithIno } = require('../utils/fileUtils')
|
||||
|
||||
@@ -74,14 +72,10 @@ class LibraryItem {
|
||||
this.media = new Book(libraryItem.media)
|
||||
} else if (this.mediaType === 'podcast') {
|
||||
this.media = new Podcast(libraryItem.media)
|
||||
} else if (this.mediaType === 'video') {
|
||||
this.media = new Video(libraryItem.media)
|
||||
} else if (this.mediaType === 'music') {
|
||||
this.media = new Music(libraryItem.media)
|
||||
}
|
||||
this.media.libraryItemId = this.id
|
||||
|
||||
this.libraryFiles = libraryItem.libraryFiles.map(f => new LibraryFile(f))
|
||||
this.libraryFiles = libraryItem.libraryFiles.map((f) => new LibraryFile(f))
|
||||
|
||||
// Migration for v2.2.23 to set ebook library files as supplementary
|
||||
if (this.isBook && this.media.ebookFile) {
|
||||
@@ -91,7 +85,6 @@ class LibraryItem {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
@@ -115,7 +108,7 @@ class LibraryItem {
|
||||
isInvalid: !!this.isInvalid,
|
||||
mediaType: this.mediaType,
|
||||
media: this.media.toJSON(),
|
||||
libraryFiles: this.libraryFiles.map(f => f.toJSON())
|
||||
libraryFiles: this.libraryFiles.map((f) => f.toJSON())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,21 +158,24 @@ class LibraryItem {
|
||||
isInvalid: !!this.isInvalid,
|
||||
mediaType: this.mediaType,
|
||||
media: this.media.toJSONExpanded(),
|
||||
libraryFiles: this.libraryFiles.map(f => f.toJSON()),
|
||||
libraryFiles: this.libraryFiles.map((f) => f.toJSON()),
|
||||
size: this.size
|
||||
}
|
||||
}
|
||||
|
||||
get isPodcast() { return this.mediaType === 'podcast' }
|
||||
get isBook() { return this.mediaType === 'book' }
|
||||
get isMusic() { return this.mediaType === 'music' }
|
||||
get isPodcast() {
|
||||
return this.mediaType === 'podcast'
|
||||
}
|
||||
get isBook() {
|
||||
return this.mediaType === 'book'
|
||||
}
|
||||
get size() {
|
||||
let total = 0
|
||||
this.libraryFiles.forEach((lf) => total += lf.metadata.size)
|
||||
this.libraryFiles.forEach((lf) => (total += lf.metadata.size))
|
||||
return total
|
||||
}
|
||||
get hasAudioFiles() {
|
||||
return this.libraryFiles.some(lf => lf.fileType === 'audio')
|
||||
return this.libraryFiles.some((lf) => lf.fileType === 'audio')
|
||||
}
|
||||
get hasMediaEntities() {
|
||||
return this.media.hasMediaEntities
|
||||
@@ -201,17 +197,16 @@ class LibraryItem {
|
||||
|
||||
for (const key in payload) {
|
||||
if (key === 'libraryFiles') {
|
||||
this.libraryFiles = payload.libraryFiles.map(lf => lf.clone())
|
||||
this.libraryFiles = payload.libraryFiles.map((lf) => lf.clone())
|
||||
|
||||
// Set cover image
|
||||
const imageFiles = this.libraryFiles.filter(lf => lf.fileType === 'image')
|
||||
const coverMatch = imageFiles.find(iFile => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
||||
const imageFiles = this.libraryFiles.filter((lf) => lf.fileType === 'image')
|
||||
const coverMatch = imageFiles.find((iFile) => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
||||
if (coverMatch) {
|
||||
this.media.coverPath = coverMatch.metadata.path
|
||||
} else if (imageFiles.length) {
|
||||
this.media.coverPath = imageFiles[0].metadata.path
|
||||
}
|
||||
|
||||
} else if (this[key] !== undefined && key !== 'media') {
|
||||
this[key] = payload[key]
|
||||
}
|
||||
@@ -283,46 +278,50 @@ class LibraryItem {
|
||||
|
||||
const metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`)
|
||||
|
||||
return fs.writeFile(metadataFilePath, JSON.stringify(this.media.toJSONForMetadataFile(), null, 2)).then(async () => {
|
||||
// Add metadata.json to libraryFiles array if it is new
|
||||
let metadataLibraryFile = this.libraryFiles.find(lf => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
||||
if (storeMetadataWithItem) {
|
||||
if (!metadataLibraryFile) {
|
||||
metadataLibraryFile = new LibraryFile()
|
||||
await metadataLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`)
|
||||
this.libraryFiles.push(metadataLibraryFile)
|
||||
} else {
|
||||
const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath)
|
||||
if (fileTimestamps) {
|
||||
metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs
|
||||
metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs
|
||||
metadataLibraryFile.metadata.size = fileTimestamps.size
|
||||
metadataLibraryFile.ino = fileTimestamps.ino
|
||||
return fs
|
||||
.writeFile(metadataFilePath, JSON.stringify(this.media.toJSONForMetadataFile(), null, 2))
|
||||
.then(async () => {
|
||||
// Add metadata.json to libraryFiles array if it is new
|
||||
let metadataLibraryFile = this.libraryFiles.find((lf) => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
||||
if (storeMetadataWithItem) {
|
||||
if (!metadataLibraryFile) {
|
||||
metadataLibraryFile = new LibraryFile()
|
||||
await metadataLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`)
|
||||
this.libraryFiles.push(metadataLibraryFile)
|
||||
} else {
|
||||
const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath)
|
||||
if (fileTimestamps) {
|
||||
metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs
|
||||
metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs
|
||||
metadataLibraryFile.metadata.size = fileTimestamps.size
|
||||
metadataLibraryFile.ino = fileTimestamps.ino
|
||||
}
|
||||
}
|
||||
const libraryItemDirTimestamps = await getFileTimestampsWithIno(this.path)
|
||||
if (libraryItemDirTimestamps) {
|
||||
this.mtimeMs = libraryItemDirTimestamps.mtimeMs
|
||||
this.ctimeMs = libraryItemDirTimestamps.ctimeMs
|
||||
}
|
||||
}
|
||||
const libraryItemDirTimestamps = await getFileTimestampsWithIno(this.path)
|
||||
if (libraryItemDirTimestamps) {
|
||||
this.mtimeMs = libraryItemDirTimestamps.mtimeMs
|
||||
this.ctimeMs = libraryItemDirTimestamps.ctimeMs
|
||||
}
|
||||
}
|
||||
|
||||
Logger.debug(`[LibraryItem] Success saving abmetadata to "${metadataFilePath}"`)
|
||||
Logger.debug(`[LibraryItem] Success saving abmetadata to "${metadataFilePath}"`)
|
||||
|
||||
return metadataLibraryFile
|
||||
}).catch((error) => {
|
||||
Logger.error(`[LibraryItem] Failed to save json file at "${metadataFilePath}"`, error)
|
||||
return null
|
||||
}).finally(() => {
|
||||
this.isSavingMetadata = false
|
||||
})
|
||||
return metadataLibraryFile
|
||||
})
|
||||
.catch((error) => {
|
||||
Logger.error(`[LibraryItem] Failed to save json file at "${metadataFilePath}"`, error)
|
||||
return null
|
||||
})
|
||||
.finally(() => {
|
||||
this.isSavingMetadata = false
|
||||
})
|
||||
}
|
||||
|
||||
removeLibraryFile(ino) {
|
||||
if (!ino) return false
|
||||
const libraryFile = this.libraryFiles.find(lf => lf.ino === ino)
|
||||
const libraryFile = this.libraryFiles.find((lf) => lf.ino === ino)
|
||||
if (libraryFile) {
|
||||
this.libraryFiles = this.libraryFiles.filter(lf => lf.ino !== ino)
|
||||
this.libraryFiles = this.libraryFiles.filter((lf) => lf.ino !== ino)
|
||||
this.updatedAt = Date.now()
|
||||
return true
|
||||
}
|
||||
@@ -333,15 +332,15 @@ class LibraryItem {
|
||||
* Set the EBookFile from a LibraryFile
|
||||
* If null then ebookFile will be removed from the book
|
||||
* all ebook library files that are not primary are marked as supplementary
|
||||
*
|
||||
* @param {LibraryFile} [libraryFile]
|
||||
*
|
||||
* @param {LibraryFile} [libraryFile]
|
||||
*/
|
||||
setPrimaryEbook(ebookLibraryFile = null) {
|
||||
const ebookLibraryFiles = this.libraryFiles.filter(lf => lf.isEBookFile)
|
||||
const ebookLibraryFiles = this.libraryFiles.filter((lf) => lf.isEBookFile)
|
||||
for (const libraryFile of ebookLibraryFiles) {
|
||||
libraryFile.isSupplementary = ebookLibraryFile?.ino !== libraryFile.ino
|
||||
}
|
||||
this.media.setEbookFile(ebookLibraryFile)
|
||||
}
|
||||
}
|
||||
module.exports = LibraryItem
|
||||
module.exports = LibraryItem
|
||||
|
@@ -4,7 +4,6 @@ const serverVersion = require('../../package.json').version
|
||||
const BookMetadata = require('./metadata/BookMetadata')
|
||||
const PodcastMetadata = require('./metadata/PodcastMetadata')
|
||||
const DeviceInfo = require('./DeviceInfo')
|
||||
const VideoMetadata = require('./metadata/VideoMetadata')
|
||||
|
||||
class PlaybackSession {
|
||||
constructor(session) {
|
||||
@@ -41,7 +40,6 @@ class PlaybackSession {
|
||||
// Not saved in DB
|
||||
this.lastSave = 0
|
||||
this.audioTracks = []
|
||||
this.videoTrack = null
|
||||
this.stream = null
|
||||
// Used for share sessions
|
||||
this.shareSessionId = null
|
||||
@@ -114,7 +112,6 @@ class PlaybackSession {
|
||||
startedAt: this.startedAt,
|
||||
updatedAt: this.updatedAt,
|
||||
audioTracks: this.audioTracks.map((at) => at.toJSON?.() || { ...at }),
|
||||
videoTrack: this.videoTrack?.toJSON() || null,
|
||||
libraryItem: libraryItem?.toJSONExpanded() || null
|
||||
}
|
||||
}
|
||||
@@ -157,8 +154,6 @@ class PlaybackSession {
|
||||
this.mediaMetadata = new BookMetadata(session.mediaMetadata)
|
||||
} else if (this.mediaType === 'podcast') {
|
||||
this.mediaMetadata = new PodcastMetadata(session.mediaMetadata)
|
||||
} else if (this.mediaType === 'video') {
|
||||
this.mediaMetadata = new VideoMetadata(session.mediaMetadata)
|
||||
}
|
||||
}
|
||||
this.displayTitle = session.displayTitle || ''
|
||||
|
@@ -43,14 +43,13 @@ class LibraryFile {
|
||||
if (globals.SupportedImageTypes.includes(this.metadata.format)) return 'image'
|
||||
if (globals.SupportedAudioTypes.includes(this.metadata.format)) return 'audio'
|
||||
if (globals.SupportedEbookTypes.includes(this.metadata.format)) return 'ebook'
|
||||
if (globals.SupportedVideoTypes.includes(this.metadata.format)) return 'video'
|
||||
if (globals.TextFileTypes.includes(this.metadata.format)) return 'text'
|
||||
if (globals.MetadataFileTypes.includes(this.metadata.format)) return 'metadata'
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
get isMediaFile() {
|
||||
return this.fileType === 'audio' || this.fileType === 'ebook' || this.fileType === 'video'
|
||||
return this.fileType === 'audio' || this.fileType === 'ebook'
|
||||
}
|
||||
|
||||
get isEBookFile() {
|
||||
@@ -75,4 +74,4 @@ class LibraryFile {
|
||||
this.updatedAt = Date.now()
|
||||
}
|
||||
}
|
||||
module.exports = LibraryFile
|
||||
module.exports = LibraryFile
|
||||
|
@@ -1,109 +0,0 @@
|
||||
const { VideoMimeType } = require('../../utils/constants')
|
||||
const FileMetadata = require('../metadata/FileMetadata')
|
||||
|
||||
class VideoFile {
|
||||
constructor(data) {
|
||||
this.index = null
|
||||
this.ino = null
|
||||
this.metadata = null
|
||||
this.addedAt = null
|
||||
this.updatedAt = null
|
||||
|
||||
this.format = null
|
||||
this.duration = null
|
||||
this.bitRate = null
|
||||
this.language = null
|
||||
this.codec = null
|
||||
this.timeBase = null
|
||||
this.frameRate = null
|
||||
this.width = null
|
||||
this.height = null
|
||||
this.embeddedCoverArt = null
|
||||
|
||||
this.invalid = 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,
|
||||
invalid: !!this.invalid,
|
||||
error: this.error || null,
|
||||
format: this.format,
|
||||
duration: this.duration,
|
||||
bitRate: this.bitRate,
|
||||
language: this.language,
|
||||
codec: this.codec,
|
||||
timeBase: this.timeBase,
|
||||
frameRate: this.frameRate,
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
embeddedCoverArt: this.embeddedCoverArt,
|
||||
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.invalid = !!data.invalid
|
||||
this.error = data.error || null
|
||||
|
||||
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.frameRate = data.frameRate
|
||||
this.width = data.width
|
||||
this.height = data.height
|
||||
this.embeddedCoverArt = data.embeddedCoverArt || null
|
||||
}
|
||||
|
||||
get mimeType() {
|
||||
var format = this.metadata.format.toUpperCase()
|
||||
if (VideoMimeType[format]) {
|
||||
return VideoMimeType[format]
|
||||
} else {
|
||||
return VideoMimeType.MP4
|
||||
}
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new VideoFile(this.toJSON())
|
||||
}
|
||||
|
||||
setDataFromProbe(libraryFile, probeData) {
|
||||
this.ino = libraryFile.ino || null
|
||||
|
||||
this.metadata = libraryFile.metadata.clone()
|
||||
this.addedAt = Date.now()
|
||||
this.updatedAt = Date.now()
|
||||
|
||||
const videoStream = probeData.videoStream
|
||||
|
||||
this.format = probeData.format
|
||||
this.duration = probeData.duration
|
||||
this.bitRate = videoStream.bit_rate || probeData.bitRate || null
|
||||
this.language = probeData.language
|
||||
this.codec = videoStream.codec || null
|
||||
this.timeBase = videoStream.time_base
|
||||
this.frameRate = videoStream.frame_rate || null
|
||||
this.width = videoStream.width || null
|
||||
this.height = videoStream.height || null
|
||||
this.embeddedCoverArt = probeData.embeddedCoverArt
|
||||
}
|
||||
}
|
||||
module.exports = VideoFile
|
@@ -1,45 +0,0 @@
|
||||
const Path = require('path')
|
||||
const { encodeUriPath } = require('../../utils/fileUtils')
|
||||
|
||||
class VideoTrack {
|
||||
constructor() {
|
||||
this.index = null
|
||||
this.duration = null
|
||||
this.title = null
|
||||
this.contentUrl = null
|
||||
this.mimeType = null
|
||||
this.codec = null
|
||||
this.metadata = null
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
index: this.index,
|
||||
duration: this.duration,
|
||||
title: this.title,
|
||||
contentUrl: this.contentUrl,
|
||||
mimeType: this.mimeType,
|
||||
codec: this.codec,
|
||||
metadata: this.metadata ? this.metadata.toJSON() : null
|
||||
}
|
||||
}
|
||||
|
||||
setData(itemId, videoFile) {
|
||||
this.index = videoFile.index
|
||||
this.duration = videoFile.duration
|
||||
this.title = videoFile.metadata.filename || ''
|
||||
this.contentUrl = Path.join(`${global.RouterBasePath}/api/items/${itemId}/file/${videoFile.ino}`, encodeUriPath(videoFile.metadata.relPath))
|
||||
this.mimeType = videoFile.mimeType
|
||||
this.codec = videoFile.codec
|
||||
this.metadata = videoFile.metadata.clone()
|
||||
}
|
||||
|
||||
setFromStream(title, duration, contentUrl) {
|
||||
this.index = 1
|
||||
this.duration = duration
|
||||
this.title = title
|
||||
this.contentUrl = contentUrl
|
||||
this.mimeType = 'application/vnd.apple.mpegurl'
|
||||
}
|
||||
}
|
||||
module.exports = VideoTrack
|
@@ -1,145 +0,0 @@
|
||||
const Logger = require('../../Logger')
|
||||
const AudioFile = require('../files/AudioFile')
|
||||
const AudioTrack = require('../files/AudioTrack')
|
||||
const MusicMetadata = require('../metadata/MusicMetadata')
|
||||
const { areEquivalent, copyValue } = require('../../utils/index')
|
||||
const { filePathToPOSIX } = require('../../utils/fileUtils')
|
||||
|
||||
class Music {
|
||||
constructor(music) {
|
||||
this.libraryItemId = null
|
||||
this.metadata = null
|
||||
this.coverPath = null
|
||||
this.tags = []
|
||||
this.audioFile = null
|
||||
|
||||
if (music) {
|
||||
this.construct(music)
|
||||
}
|
||||
}
|
||||
|
||||
construct(music) {
|
||||
this.libraryItemId = music.libraryItemId
|
||||
this.metadata = new MusicMetadata(music.metadata)
|
||||
this.coverPath = music.coverPath
|
||||
this.tags = [...music.tags]
|
||||
this.audioFile = new AudioFile(music.audioFile)
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
libraryItemId: this.libraryItemId,
|
||||
metadata: this.metadata.toJSON(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
audioFile: this.audioFile.toJSON(),
|
||||
}
|
||||
}
|
||||
|
||||
toJSONMinified() {
|
||||
return {
|
||||
metadata: this.metadata.toJSONMinified(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
audioFile: this.audioFile.toJSON(),
|
||||
duration: this.duration,
|
||||
size: this.size
|
||||
}
|
||||
}
|
||||
|
||||
toJSONExpanded() {
|
||||
return {
|
||||
libraryItemId: this.libraryItemId,
|
||||
metadata: this.metadata.toJSONExpanded(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
audioFile: this.audioFile.toJSON(),
|
||||
duration: this.duration,
|
||||
size: this.size
|
||||
}
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.audioFile.metadata.size
|
||||
}
|
||||
get hasMediaEntities() {
|
||||
return !!this.audioFile
|
||||
}
|
||||
get duration() {
|
||||
return this.audioFile.duration || 0
|
||||
}
|
||||
get audioTrack() {
|
||||
const audioTrack = new AudioTrack()
|
||||
audioTrack.setData(this.libraryItemId, this.audioFile, 0)
|
||||
return audioTrack
|
||||
}
|
||||
get numTracks() {
|
||||
return 1
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
const json = this.toJSON()
|
||||
delete json.episodes // do not update media entities here
|
||||
let hasUpdates = false
|
||||
for (const key in json) {
|
||||
if (payload[key] !== undefined) {
|
||||
if (key === 'metadata') {
|
||||
if (this.metadata.update(payload.metadata)) {
|
||||
hasUpdates = true
|
||||
}
|
||||
} else if (!areEquivalent(payload[key], json[key])) {
|
||||
this[key] = copyValue(payload[key])
|
||||
Logger.debug('[Podcast] Key updated', key, this[key])
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
updateCover(coverPath) {
|
||||
coverPath = filePathToPOSIX(coverPath)
|
||||
if (this.coverPath === coverPath) return false
|
||||
this.coverPath = coverPath
|
||||
return true
|
||||
}
|
||||
|
||||
removeFileWithInode(inode) {
|
||||
return false
|
||||
}
|
||||
|
||||
findFileWithInode(inode) {
|
||||
return (this.audioFile && this.audioFile.ino === inode) ? this.audioFile : null
|
||||
}
|
||||
|
||||
setData(mediaData) {
|
||||
this.metadata = new MusicMetadata()
|
||||
if (mediaData.metadata) {
|
||||
this.metadata.setData(mediaData.metadata)
|
||||
}
|
||||
|
||||
this.coverPath = mediaData.coverPath || null
|
||||
}
|
||||
|
||||
setAudioFile(audioFile) {
|
||||
this.audioFile = audioFile
|
||||
}
|
||||
|
||||
// Only checks container format
|
||||
checkCanDirectPlay(payload) {
|
||||
return true
|
||||
}
|
||||
|
||||
getDirectPlayTracklist() {
|
||||
return [this.audioTrack]
|
||||
}
|
||||
|
||||
getPlaybackTitle() {
|
||||
return this.metadata.title
|
||||
}
|
||||
|
||||
getPlaybackAuthor() {
|
||||
return this.metadata.artist
|
||||
}
|
||||
}
|
||||
module.exports = Music
|
@@ -1,137 +0,0 @@
|
||||
const Logger = require('../../Logger')
|
||||
const VideoFile = require('../files/VideoFile')
|
||||
const VideoTrack = require('../files/VideoTrack')
|
||||
const VideoMetadata = require('../metadata/VideoMetadata')
|
||||
const { areEquivalent, copyValue } = require('../../utils/index')
|
||||
const { filePathToPOSIX } = require('../../utils/fileUtils')
|
||||
|
||||
class Video {
|
||||
constructor(video) {
|
||||
this.libraryItemId = null
|
||||
this.metadata = null
|
||||
this.coverPath = null
|
||||
this.tags = []
|
||||
this.episodes = []
|
||||
|
||||
this.autoDownloadEpisodes = false
|
||||
this.lastEpisodeCheck = 0
|
||||
|
||||
this.lastCoverSearch = null
|
||||
this.lastCoverSearchQuery = null
|
||||
|
||||
if (video) {
|
||||
this.construct(video)
|
||||
}
|
||||
}
|
||||
|
||||
construct(video) {
|
||||
this.libraryItemId = video.libraryItemId
|
||||
this.metadata = new VideoMetadata(video.metadata)
|
||||
this.coverPath = video.coverPath
|
||||
this.tags = [...video.tags]
|
||||
this.videoFile = new VideoFile(video.videoFile)
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
libraryItemId: this.libraryItemId,
|
||||
metadata: this.metadata.toJSONExpanded(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
videoFile: this.videoFile.toJSON()
|
||||
}
|
||||
}
|
||||
|
||||
toJSONMinified() {
|
||||
return {
|
||||
metadata: this.metadata.toJSONMinified(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
videoFile: this.videoFile.toJSON(),
|
||||
size: this.size
|
||||
}
|
||||
}
|
||||
|
||||
toJSONExpanded() {
|
||||
return {
|
||||
libraryItemId: this.libraryItemId,
|
||||
metadata: this.metadata.toJSONExpanded(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
videoFile: this.videoFile.toJSON(),
|
||||
size: this.size
|
||||
}
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.videoFile.metadata.size
|
||||
}
|
||||
get hasMediaEntities() {
|
||||
return true
|
||||
}
|
||||
get duration() {
|
||||
return 0
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
var json = this.toJSON()
|
||||
var hasUpdates = false
|
||||
for (const key in json) {
|
||||
if (payload[key] !== undefined) {
|
||||
if (key === 'metadata') {
|
||||
if (this.metadata.update(payload.metadata)) {
|
||||
hasUpdates = true
|
||||
}
|
||||
} else if (!areEquivalent(payload[key], json[key])) {
|
||||
this[key] = copyValue(payload[key])
|
||||
Logger.debug('[Video] Key updated', key, this[key])
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
updateCover(coverPath) {
|
||||
coverPath = filePathToPOSIX(coverPath)
|
||||
if (this.coverPath === coverPath) return false
|
||||
this.coverPath = coverPath
|
||||
return true
|
||||
}
|
||||
|
||||
removeFileWithInode(inode) {
|
||||
|
||||
}
|
||||
|
||||
findFileWithInode(inode) {
|
||||
return null
|
||||
}
|
||||
|
||||
setVideoFile(videoFile) {
|
||||
this.videoFile = videoFile
|
||||
}
|
||||
|
||||
setData(mediaMetadata) {
|
||||
this.metadata = new VideoMetadata()
|
||||
if (mediaMetadata.metadata) {
|
||||
this.metadata.setData(mediaMetadata.metadata)
|
||||
}
|
||||
|
||||
this.coverPath = mediaMetadata.coverPath || null
|
||||
}
|
||||
|
||||
getPlaybackTitle() {
|
||||
return this.metadata.title
|
||||
}
|
||||
|
||||
getPlaybackAuthor() {
|
||||
return ''
|
||||
}
|
||||
|
||||
getVideoTrack() {
|
||||
var track = new VideoTrack()
|
||||
track.setData(this.libraryItemId, this.videoFile)
|
||||
return track
|
||||
}
|
||||
}
|
||||
module.exports = Video
|
@@ -1,307 +0,0 @@
|
||||
const Logger = require('../../Logger')
|
||||
const { areEquivalent, copyValue, 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)
|
||||
}
|
||||
|
||||
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
|
@@ -1,80 +0,0 @@
|
||||
const Logger = require('../../Logger')
|
||||
const { areEquivalent, copyValue, getTitleIgnorePrefix, getTitlePrefixAtEnd } = require('../../utils/index')
|
||||
|
||||
class VideoMetadata {
|
||||
constructor(metadata) {
|
||||
this.title = null
|
||||
this.description = null
|
||||
this.explicit = false
|
||||
this.language = null
|
||||
|
||||
if (metadata) {
|
||||
this.construct(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
construct(metadata) {
|
||||
this.title = metadata.title
|
||||
this.description = metadata.description
|
||||
this.explicit = metadata.explicit
|
||||
this.language = metadata.language || null
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
title: this.title,
|
||||
description: this.description,
|
||||
explicit: this.explicit,
|
||||
language: this.language
|
||||
}
|
||||
}
|
||||
|
||||
toJSONMinified() {
|
||||
return {
|
||||
title: this.title,
|
||||
titleIgnorePrefix: this.titlePrefixAtEnd,
|
||||
description: this.description,
|
||||
explicit: this.explicit,
|
||||
language: this.language
|
||||
}
|
||||
}
|
||||
|
||||
toJSONExpanded() {
|
||||
return this.toJSONMinified()
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new VideoMetadata(this.toJSON())
|
||||
}
|
||||
|
||||
get titleIgnorePrefix() {
|
||||
return getTitleIgnorePrefix(this.title)
|
||||
}
|
||||
|
||||
get titlePrefixAtEnd() {
|
||||
return getTitlePrefixAtEnd(this.title)
|
||||
}
|
||||
|
||||
setData(mediaMetadata = {}) {
|
||||
this.title = mediaMetadata.title || null
|
||||
this.description = mediaMetadata.description || null
|
||||
this.explicit = !!mediaMetadata.explicit
|
||||
this.language = mediaMetadata.language || null
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
var json = this.toJSON()
|
||||
var hasUpdates = false
|
||||
for (const key in json) {
|
||||
if (payload[key] !== undefined) {
|
||||
if (!areEquivalent(payload[key], json[key])) {
|
||||
this[key] = copyValue(payload[key])
|
||||
Logger.debug('[VideoMetadata] Key updated', key, this[key])
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasUpdates
|
||||
}
|
||||
}
|
||||
module.exports = VideoMetadata
|
Reference in New Issue
Block a user