Laying the groundwork for music media type #964

This commit is contained in:
advplyr 2022-12-22 16:38:55 -06:00
parent c3717f6979
commit b884f8fe11
18 changed files with 491 additions and 134 deletions

View file

@ -57,7 +57,9 @@ class Library {
else if (this.icon === 'comic') this.icon = 'file-picture'
else this.icon = 'database'
}
if (!this.mediaType || (this.mediaType !== 'podcast' && this.mediaType !== 'book' && this.mediaType !== 'video')) {
const mediaTypes = ['podcast', 'book', 'video', 'music']
if (!this.mediaType || !mediaTypes.includes(this.mediaType)) {
this.mediaType = 'book'
}
}

View file

@ -7,6 +7,7 @@ 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, getId, cleanStringForSearch } = require('../utils/index')
class LibraryItem {
@ -72,6 +73,8 @@ class LibraryItem {
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
@ -153,13 +156,14 @@ class LibraryItem {
get isPodcast() { return this.mediaType === 'podcast' }
get isBook() { return this.mediaType === 'book' }
get isMusic() { return this.mediaType === 'music' }
get size() {
var total = 0
let total = 0
this.libraryFiles.forEach((lf) => total += lf.metadata.size)
return total
}
get audioFileTotalSize() {
var total = 0
let total = 0
this.libraryFiles.filter(lf => lf.fileType == 'audio').forEach((lf) => total += lf.metadata.size)
return total
}
@ -182,8 +186,10 @@ class LibraryItem {
this.media = new Video()
} else if (libraryMediaType === 'podcast') {
this.media = new Podcast()
} else {
} else if (libraryMediaType === 'book') {
this.media = new Book()
} else if (libraryMediaType === 'music') {
this.media = new Music()
}
this.media.libraryItemId = this.id
@ -348,11 +354,11 @@ class LibraryItem {
}
})
var newLibraryFiles = []
var existingLibraryFiles = []
const newLibraryFiles = []
const existingLibraryFiles = []
dataFound.libraryFiles.forEach((lf) => {
var fileFoundCheck = this.checkFileFound(lf, true)
const fileFoundCheck = this.checkFileFound(lf, true)
if (fileFoundCheck === null) {
newLibraryFiles.push(lf)
} else if (fileFoundCheck && lf.metadata.format !== 'abs') { // Ignore abs file updates
@ -397,7 +403,7 @@ class LibraryItem {
// If cover path is in item folder, make sure libraryFile exists for it
if (this.media.coverPath && this.media.coverPath.startsWith(this.path)) {
var lf = this.libraryFiles.find(lf => lf.metadata.path === this.media.coverPath)
const lf = this.libraryFiles.find(lf => lf.metadata.path === this.media.coverPath)
if (!lf) {
Logger.warn(`[LibraryItem] Invalid cover path - library file dne "${this.media.coverPath}"`)
this.media.updateCover('')
@ -419,7 +425,7 @@ class LibraryItem {
// Set metadata from files
async syncFiles(preferOpfMetadata) {
var hasUpdated = false
let hasUpdated = false
if (this.mediaType === 'book') {
// Add/update ebook file (ebooks that were removed are removed in checkScanData)
@ -436,7 +442,7 @@ class LibraryItem {
}
// Set cover image if not set
var imageFiles = this.libraryFiles.filter(lf => lf.fileType === 'image')
const imageFiles = this.libraryFiles.filter(lf => lf.fileType === 'image')
if (imageFiles.length && !this.media.coverPath) {
this.media.coverPath = imageFiles[0].metadata.path
Logger.debug('[LibraryItem] Set media cover path', this.media.coverPath)
@ -444,7 +450,7 @@ class LibraryItem {
}
// Parse metadata files
var textMetadataFiles = this.libraryFiles.filter(lf => lf.fileType === 'metadata' || lf.fileType === 'text')
const textMetadataFiles = this.libraryFiles.filter(lf => lf.fileType === 'metadata' || lf.fileType === 'text')
if (textMetadataFiles.length) {
if (await this.media.syncMetadataFiles(textMetadataFiles, preferOpfMetadata)) {
hasUpdated = true
@ -468,12 +474,12 @@ class LibraryItem {
// Saves metadata.abs file
async saveMetadata() {
if (this.mediaType === 'video') return
if (this.mediaType === 'video' || this.mediaType === 'music') return
if (this.isSavingMetadata) return
this.isSavingMetadata = true
var metadataPath = Path.join(global.MetadataPath, 'items', this.id)
let metadataPath = Path.join(global.MetadataPath, 'items', this.id)
if (global.ServerSettings.storeMetadataWithItem && !this.isFile) {
metadataPath = this.path
} else {

View file

@ -148,7 +148,7 @@ class PodcastEpisode {
// Only checks container format
checkCanDirectPlay(payload) {
var supportedMimeTypes = payload.supportedMimeTypes || []
const supportedMimeTypes = payload.supportedMimeTypes || []
return supportedMimeTypes.includes(this.audioFile.mimeType)
}

View file

@ -118,9 +118,9 @@ class Book {
return this.missingParts.length || this.invalidAudioFiles.length
}
get tracks() {
var startOffset = 0
let startOffset = 0
return this.includedAudioFiles.map((af) => {
var audioTrack = new AudioTrack()
const audioTrack = new AudioTrack()
audioTrack.setData(this.libraryItemId, af, startOffset)
startOffset += audioTrack.duration
return audioTrack

View file

@ -0,0 +1,159 @@
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')
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(),
size: this.size
}
}
toJSONExpanded() {
return {
libraryItemId: this.libraryItemId,
metadata: this.metadata.toJSONExpanded(),
coverPath: this.coverPath,
tags: [...this.tags],
audioFile: this.audioFile.toJSON(),
size: this.size
}
}
get size() {
return this.audioFile.metadata.size
}
get hasMediaEntities() {
return !!this.audioFile
}
get shouldSearchForCover() {
return false
}
get hasEmbeddedCoverArt() {
return this.audioFile.embeddedCoverArt
}
get hasIssues() {
return false
}
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 = coverPath.replace(/\\/g, '/')
if (this.coverPath === coverPath) return false
this.coverPath = coverPath
return true
}
removeFileWithInode(inode) {
return false
}
findFileWithInode(inode) {
return this.audioFile && this.audioFile.ino === inode
}
setData(mediaData) {
this.metadata = new MusicMetadata()
if (mediaData.metadata) {
this.metadata.setData(mediaData.metadata)
}
this.coverPath = mediaData.coverPath || null
}
setAudioFile(audioFile) {
this.audioFile = audioFile
}
syncMetadataFiles(textMetadataFiles, opfMetadataOverrideDetails) {
return false
}
searchQuery(query) {
return {}
}
// 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

View file

@ -0,0 +1,104 @@
const Logger = require('../../Logger')
const { areEquivalent, copyValue, cleanStringForSearch, getTitleIgnorePrefix, getTitlePrefixAtEnd } = require('../../utils/index')
class MusicMetadata {
constructor(metadata) {
this.title = null
this.artist = null
this.album = null
this.genres = [] // Array of strings
this.releaseDate = null
this.language = null
this.explicit = false
if (metadata) {
this.construct(metadata)
}
}
construct(metadata) {
this.title = metadata.title
this.artist = metadata.artist
this.album = metadata.album
this.genres = metadata.genres ? [...metadata.genres] : []
this.releaseDate = metadata.releaseDate || null
this.language = metadata.language
this.explicit = !!metadata.explicit
}
toJSON() {
return {
title: this.title,
artist: this.artist,
album: this.album,
genres: [...this.genres],
releaseDate: this.releaseDate,
language: this.language,
explicit: this.explicit
}
}
toJSONMinified() {
return {
title: this.title,
titleIgnorePrefix: this.titlePrefixAtEnd,
artist: this.artist,
album: this.album,
genres: [...this.genres],
releaseDate: this.releaseDate,
language: this.language,
explicit: this.explicit
}
}
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', 'artist', '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
}
}
module.exports = MusicMetadata