mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-20 18:59:37 +00:00
New data model removing media entity for books
This commit is contained in:
parent
920ca683b9
commit
3150822117
44 changed files with 733 additions and 798 deletions
|
|
@ -358,8 +358,8 @@ class LibraryItem {
|
|||
return true
|
||||
})
|
||||
if (filesRemoved.length) {
|
||||
if (this.media.audiobooks && this.media.audiobooks.length) {
|
||||
this.media.audiobooks.forEach(ab => ab.checkUpdateMissingTracks())
|
||||
if (this.media.mediaType === 'book') {
|
||||
this.media.checkUpdateMissingTracks()
|
||||
}
|
||||
hasUpdated = true
|
||||
}
|
||||
|
|
@ -404,17 +404,14 @@ class LibraryItem {
|
|||
var hasUpdated = false
|
||||
|
||||
if (this.mediaType === 'book') {
|
||||
// Add/update ebook files (ebooks that were removed are removed in checkScanData)
|
||||
// Add/update ebook file (ebooks that were removed are removed in checkScanData)
|
||||
this.libraryFiles.forEach((lf) => {
|
||||
if (lf.fileType === 'ebook') {
|
||||
var existingFile = this.media.findFileWithInode(lf.ino)
|
||||
if (!existingFile) {
|
||||
this.media.addEbookFile(lf)
|
||||
if (!this.media.ebookFile) {
|
||||
this.media.setEbookFile(lf)
|
||||
hasUpdated = true
|
||||
} else if (this.media.ebookFile.ino == lf.ino && this.media.ebookFile.updateFromLibraryFile(lf)) { // Update existing ebookFile
|
||||
hasUpdated = true
|
||||
} else if (existingFile.ebookFormat) {
|
||||
if (existingFile.updateFromLibraryFile(lf)) {// EBookFile.js
|
||||
hasUpdated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -447,8 +444,8 @@ class LibraryItem {
|
|||
return this.media.searchQuery(query)
|
||||
}
|
||||
|
||||
getPlaybackMediaEntity() {
|
||||
return this.media.getPlaybackMediaEntity()
|
||||
getDirectPlayTracklist(libraryItemId) {
|
||||
return this.media.getDirectPlayTracklist(libraryItemId)
|
||||
}
|
||||
}
|
||||
module.exports = LibraryItem
|
||||
|
|
@ -9,10 +9,10 @@ class PlaybackSession {
|
|||
this.id = null
|
||||
this.userId = null
|
||||
this.libraryItemId = null
|
||||
this.mediaEntityId = null
|
||||
|
||||
this.mediaType = null
|
||||
this.mediaMetadata = null
|
||||
this.coverPath = null
|
||||
this.duration = null
|
||||
|
||||
this.playMethod = null
|
||||
|
|
@ -41,9 +41,9 @@ class PlaybackSession {
|
|||
sessionType: this.sessionType,
|
||||
userId: this.userId,
|
||||
libraryItemId: this.libraryItemId,
|
||||
mediaEntityId: this.mediaEntityId,
|
||||
mediaType: this.mediaType,
|
||||
mediaMetadata: this.mediaMetadata ? this.mediaMetadata.toJSON() : null,
|
||||
coverPath: this.coverPath,
|
||||
duration: this.duration,
|
||||
playMethod: this.playMethod,
|
||||
date: this.date,
|
||||
|
|
@ -54,15 +54,15 @@ class PlaybackSession {
|
|||
}
|
||||
}
|
||||
|
||||
toJSONForClient() {
|
||||
toJSONForClient(libraryItem) {
|
||||
return {
|
||||
id: this.id,
|
||||
sessionType: this.sessionType,
|
||||
userId: this.userId,
|
||||
libraryItemId: this.libraryItemId,
|
||||
mediaEntityId: this.mediaEntityId,
|
||||
mediaType: this.mediaType,
|
||||
mediaMetadata: this.mediaMetadata ? this.mediaMetadata.toJSON() : null,
|
||||
coverPath: this.coverPath,
|
||||
duration: this.duration,
|
||||
playMethod: this.playMethod,
|
||||
date: this.date,
|
||||
|
|
@ -71,7 +71,8 @@ class PlaybackSession {
|
|||
lastUpdate: this.lastUpdate,
|
||||
updatedAt: this.updatedAt,
|
||||
audioTracks: this.audioTracks.map(at => at.toJSON()),
|
||||
currentTime: this.currentTime
|
||||
currentTime: this.currentTime,
|
||||
libraryItem: libraryItem.toJSONExpanded()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +81,6 @@ class PlaybackSession {
|
|||
this.sessionType = session.sessionType
|
||||
this.userId = session.userId
|
||||
this.libraryItemId = session.libraryItemId
|
||||
this.mediaEntityId = session.mediaEntityId
|
||||
this.mediaType = session.mediaType
|
||||
this.duration = session.duration
|
||||
this.playMethod = session.playMethod
|
||||
|
|
@ -93,7 +93,7 @@ class PlaybackSession {
|
|||
this.mediaMetadata = new PodcastMetadata(session.mediaMetadata)
|
||||
}
|
||||
}
|
||||
|
||||
this.coverPath = session.coverPath
|
||||
this.date = session.date
|
||||
this.dayOfWeek = session.dayOfWeek
|
||||
|
||||
|
|
@ -107,14 +107,14 @@ class PlaybackSession {
|
|||
return Math.max(0, Math.min(this.currentTime / this.duration, 1))
|
||||
}
|
||||
|
||||
setData(libraryItem, mediaEntity, user) {
|
||||
setData(libraryItem, user) {
|
||||
this.id = getId('play')
|
||||
this.userId = user.id
|
||||
this.libraryItemId = libraryItem.id
|
||||
this.mediaEntityId = mediaEntity.id
|
||||
this.mediaType = libraryItem.mediaType
|
||||
this.mediaMetadata = libraryItem.media.metadata.clone()
|
||||
this.duration = mediaEntity.duration
|
||||
this.coverPath = libraryItem.media.coverPath
|
||||
this.duration = libraryItem.media.duration
|
||||
|
||||
this.timeListening = 0
|
||||
this.date = date.format(new Date(), 'YYYY-MM-DD')
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ class ServerSettings {
|
|||
this.coverAspectRatio = BookCoverAspectRatio.SQUARE
|
||||
this.bookshelfView = BookshelfView.STANDARD
|
||||
|
||||
// Podcasts
|
||||
this.podcastEpisodeSchedule = '0 * * * *' // Every hour
|
||||
|
||||
this.sortingIgnorePrefix = false
|
||||
this.chromecastEnabled = false
|
||||
this.logLevel = Logger.logLevel
|
||||
|
|
|
|||
|
|
@ -9,13 +9,12 @@ const hlsPlaylistGenerator = require('../utils/hlsPlaylistGenerator')
|
|||
const AudioTrack = require('./files/AudioTrack')
|
||||
|
||||
class Stream extends EventEmitter {
|
||||
constructor(sessionId, streamPath, user, libraryItem, mediaEntity, startTime, clientEmitter, transcodeOptions = {}) {
|
||||
constructor(sessionId, streamPath, user, libraryItem, startTime, clientEmitter, transcodeOptions = {}) {
|
||||
super()
|
||||
|
||||
this.id = sessionId
|
||||
this.user = user
|
||||
this.libraryItem = libraryItem
|
||||
this.mediaEntity = mediaEntity
|
||||
this.clientEmitter = clientEmitter
|
||||
|
||||
this.transcodeOptions = transcodeOptions
|
||||
|
|
@ -46,17 +45,12 @@ class Stream extends EventEmitter {
|
|||
get mediaTitle() {
|
||||
return this.libraryItem.media.metadata.title || ''
|
||||
}
|
||||
get mediaEntityName() {
|
||||
return this.mediaEntity.name
|
||||
}
|
||||
get itemTitle() {
|
||||
return `${this.mediaTitle} (${this.mediaEntityName})`
|
||||
}
|
||||
get totalDuration() {
|
||||
return this.mediaEntity.duration
|
||||
return this.libraryItem.media.duration
|
||||
}
|
||||
get tracks() {
|
||||
return this.mediaEntity.tracks
|
||||
// TODO: Podcast episode tracks
|
||||
return this.libraryItem.media.tracks
|
||||
}
|
||||
get tracksAudioFileType() {
|
||||
if (!this.tracks.length) return null
|
||||
|
|
@ -226,7 +220,7 @@ class Stream extends EventEmitter {
|
|||
if (!this.isTranscodeComplete) {
|
||||
this.checkFiles()
|
||||
} else {
|
||||
Logger.info(`[Stream] ${this.itemTitle} sending stream_ready`)
|
||||
Logger.info(`[Stream] ${this.mediaTitle} sending stream_ready`)
|
||||
this.clientEmit('stream_ready')
|
||||
clearInterval(intervalId)
|
||||
}
|
||||
|
|
@ -414,7 +408,7 @@ class Stream extends EventEmitter {
|
|||
|
||||
getAudioTrack() {
|
||||
var newAudioTrack = new AudioTrack()
|
||||
newAudioTrack.setFromStream(this.itemTitle, this.totalDuration, this.clientPlaylistUri)
|
||||
newAudioTrack.setFromStream(this.mediaTitle, this.totalDuration, this.clientPlaylistUri)
|
||||
return newAudioTrack
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ class PodcastEpisode {
|
|||
}
|
||||
}
|
||||
|
||||
get isPlaybackMediaEntity() { return true }
|
||||
get tracks() {
|
||||
return [this.audioFile]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,9 @@ const abmetadataGenerator = require('../../utils/abmetadataGenerator')
|
|||
const { areEquivalent, copyValue } = require('../../utils/index')
|
||||
const { parseOpfMetadataXML } = require('../../utils/parseOpfMetadata')
|
||||
const { readTextFile } = require('../../utils/fileUtils')
|
||||
|
||||
const AudioFile = require('../files/AudioFile')
|
||||
const AudioTrack = require('../files/AudioTrack')
|
||||
const EBookFile = require('../files/EBookFile')
|
||||
const Audiobook = require('../entities/Audiobook')
|
||||
const EBook = require('../entities/EBook')
|
||||
|
||||
class Book {
|
||||
constructor(book) {
|
||||
|
|
@ -17,8 +16,10 @@ class Book {
|
|||
this.coverPath = null
|
||||
this.tags = []
|
||||
|
||||
this.audiobooks = []
|
||||
this.ebooks = []
|
||||
this.audioFiles = []
|
||||
this.chapters = []
|
||||
this.missingParts = []
|
||||
this.ebookFile = null
|
||||
|
||||
this.lastCoverSearch = null
|
||||
this.lastCoverSearchQuery = null
|
||||
|
|
@ -32,8 +33,10 @@ class Book {
|
|||
this.metadata = new BookMetadata(book.metadata)
|
||||
this.coverPath = book.coverPath
|
||||
this.tags = [...book.tags]
|
||||
this.audiobooks = book.audiobooks.map(ab => new Audiobook(ab))
|
||||
this.ebooks = book.ebooks.map(eb => new EBook(eb))
|
||||
this.audioFiles = book.audioFiles.map(f => new AudioFile(f))
|
||||
this.chapters = book.chapters.map(c => ({ ...c }))
|
||||
this.missingParts = book.missingParts ? [...book.missingParts] : []
|
||||
this.ebookFile = book.ebookFile ? new EBookFile(book.ebookFile) : null
|
||||
this.lastCoverSearch = book.lastCoverSearch || null
|
||||
this.lastCoverSearchQuery = book.lastCoverSearchQuery || null
|
||||
}
|
||||
|
|
@ -43,8 +46,10 @@ class Book {
|
|||
metadata: this.metadata.toJSON(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
audiobooks: this.audiobooks.map(ab => ab.toJSON()),
|
||||
ebooks: this.ebooks.map(eb => eb.toJSON())
|
||||
audioFiles: this.audioFiles.map(f => f.toJSON()),
|
||||
chapters: this.chapters.map(c => ({ ...c })),
|
||||
missingParts: [...this.missingParts],
|
||||
ebookFile: this.ebookFile ? this.ebookFile.toJSON() : null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,9 +58,13 @@ class Book {
|
|||
metadata: this.metadata.toJSON(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
audiobooks: this.audiobooks.map(ab => ab.toJSONMinified()),
|
||||
ebooks: this.ebooks.map(eb => eb.toJSONMinified()),
|
||||
size: this.size
|
||||
numTracks: this.tracks.length,
|
||||
numAudioFiles: this.audioFiles.length,
|
||||
numChapters: this.chapters.length,
|
||||
numMissingParts: this.missingParts.length,
|
||||
duration: this.duration,
|
||||
size: this.size,
|
||||
ebookFormat: this.ebookFile ? this.ebookFile.ebookFormat : null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -64,24 +73,26 @@ class Book {
|
|||
metadata: this.metadata.toJSONExpanded(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
audiobooks: this.audiobooks.map(ab => ab.toJSONExpanded()),
|
||||
ebooks: this.ebooks.map(eb => eb.toJSONExpanded()),
|
||||
audioFiles: this.audioFiles.map(f => f.toJSON()),
|
||||
chapters: this.chapters.map(c => ({ ...c })),
|
||||
duration: this.duration,
|
||||
size: this.size,
|
||||
tracks: this.tracks.map(t => t.toJSON()),
|
||||
missingParts: [...this.missingParts],
|
||||
ebookFile: this.ebookFile ? this.ebookFile.toJSON() : null
|
||||
}
|
||||
}
|
||||
|
||||
get size() {
|
||||
var total = 0
|
||||
this.audiobooks.forEach((ab) => {
|
||||
total += ab.size
|
||||
})
|
||||
this.ebooks.forEach((eb) => {
|
||||
total += eb.size
|
||||
})
|
||||
this.audioFiles.forEach((af) => total += af.metadata.size)
|
||||
if (this.ebookFile) {
|
||||
total += this.ebookFile.metadata.size
|
||||
}
|
||||
return total
|
||||
}
|
||||
get hasMediaEntities() {
|
||||
return !!(this.audiobooks.length + this.ebooks.length)
|
||||
return !!this.tracks.length || this.ebookFile
|
||||
}
|
||||
get shouldSearchForCover() {
|
||||
if (this.coverPath) return false
|
||||
|
|
@ -89,10 +100,22 @@ class Book {
|
|||
return (Date.now() - this.lastCoverSearch) > 1000 * 60 * 60 * 24 * 7 // 7 day
|
||||
}
|
||||
get hasEmbeddedCoverArt() {
|
||||
return this.audiobooks.some(ab => ab.hasEmbeddedCoverArt)
|
||||
return this.audioFiles.some(af => af.embeddedCoverArt)
|
||||
}
|
||||
get hasIssues() {
|
||||
return this.audiobooks.some(ab => ab.missingParts.length)
|
||||
return this.missingParts.length || this.audioFiles.some(af => af.invalid)
|
||||
}
|
||||
get tracks() {
|
||||
|
||||
return this.audioFiles.filter(af => !af.exclude && !af.invalid)
|
||||
}
|
||||
get duration() {
|
||||
var total = 0
|
||||
this.tracks.forEach((track) => total += track.duration)
|
||||
return total
|
||||
}
|
||||
get numTracks() {
|
||||
return this.tracks.length
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
|
|
@ -123,42 +146,22 @@ class Book {
|
|||
this.coverPath = coverPath
|
||||
return true
|
||||
}
|
||||
|
||||
getAudiobookById(audiobookId) {
|
||||
return this.audiobooks.find(ab => ab.id === audiobookId)
|
||||
}
|
||||
getMediaEntityById(entityId) {
|
||||
var ent = this.audiobooks.find(ab => ab.id === entityId)
|
||||
if (ent) return ent
|
||||
return this.ebooks.find(eb => eb.id === entityId)
|
||||
}
|
||||
getPlaybackMediaEntity() { // Get first playback media entity
|
||||
if (!this.audiobooks.length) return null
|
||||
return this.audiobooks[0]
|
||||
}
|
||||
|
||||
removeFileWithInode(inode) {
|
||||
var audiobookWithIno = this.audiobooks.find(ab => ab.findFileWithInode(inode))
|
||||
if (audiobookWithIno) {
|
||||
audiobookWithIno.removeFileWithInode(inode)
|
||||
if (!audiobookWithIno.audioFiles.length) { // All audio files removed = remove audiobook
|
||||
this.audiobooks = this.audiobooks.filter(ab => ab.id !== audiobookWithIno.id)
|
||||
}
|
||||
if (this.audioFiles.some(af => af.ino === inode)) {
|
||||
this.audioFiles = this.audioFiles.filter(af => af.ino !== inode)
|
||||
return true
|
||||
}
|
||||
var ebookWithIno = this.ebooks.find(eb => eb.findFileWithInode(inode))
|
||||
if (ebookWithIno) {
|
||||
this.ebooks = this.ebooks.filter(eb => eb.id !== ebookWithIno.id) // Remove ebook
|
||||
if (this.ebookFile && this.ebookFile.ino === inode) {
|
||||
this.ebookFile = null
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
findFileWithInode(inode) {
|
||||
var audioFile = this.audiobooks.find(ab => ab.findFileWithInode(inode))
|
||||
var audioFile = this.audioFiles.find(af => af.ino === inode)
|
||||
if (audioFile) return audioFile
|
||||
var ebookFile = this.ebooks.find(eb => eb.findFileWithInode(inode))
|
||||
if (ebookFile) return ebookFile
|
||||
if (this.ebookFile && this.ebookFile.ino === inode) return this.ebookFile
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
@ -169,9 +172,8 @@ class Book {
|
|||
|
||||
// Audio file metadata tags map to book details (will not overwrite)
|
||||
setMetadataFromAudioFile(overrideExistingDetails = false) {
|
||||
if (!this.audiobooks.length) return false
|
||||
var audiobook = this.audiobooks[0]
|
||||
var audioFile = audiobook.audioFiles[0]
|
||||
if (!this.audioFiles.length) return false
|
||||
var audioFile = this.audioFiles[0]
|
||||
if (!audioFile.metaTags) return false
|
||||
return this.metadata.setDataFromAudioMetaTags(audioFile.metaTags, overrideExistingDetails)
|
||||
}
|
||||
|
|
@ -276,50 +278,135 @@ class Book {
|
|||
return payload
|
||||
}
|
||||
|
||||
addEbookFile(libraryFile) {
|
||||
setEbookFile(libraryFile) {
|
||||
var ebookFile = new EBookFile()
|
||||
ebookFile.setData(libraryFile)
|
||||
|
||||
var ebookIndex = this.ebooks.length + 1
|
||||
var newEBook = new EBook()
|
||||
newEBook.setData(ebookFile, ebookIndex)
|
||||
this.ebooks.push(newEBook)
|
||||
this.ebookFile = ebookFile
|
||||
}
|
||||
|
||||
getCreateAudiobookVariant(variant) {
|
||||
if (this.audiobooks.length) {
|
||||
var ab = this.audiobooks.find(ab => ab.name == variantName)
|
||||
if (ab) return ab
|
||||
}
|
||||
var abIndex = this.audiobooks.length + 1
|
||||
var newAb = new Audiobook()
|
||||
newAb.setData(variant, abIndex)
|
||||
this.audiobooks.push(newAb)
|
||||
return newAb
|
||||
addAudioFile(audioFile) {
|
||||
this.audioFiles.push(audioFile)
|
||||
}
|
||||
|
||||
addAudioFileToAudiobook(audioFile, variant = 'default') { // Create if none
|
||||
var audiobook = this.getCreateAudiobookVariant(variant)
|
||||
audiobook.audioFiles.push(audioFile)
|
||||
}
|
||||
|
||||
getLongestDuration() {
|
||||
if (!this.audiobooks.length) return 0
|
||||
var longest = 0
|
||||
this.audiobooks.forEach((ab) => {
|
||||
if (ab.duration > longest) longest = ab.duration
|
||||
updateAudioTracks(orderedFileData) {
|
||||
var index = 1
|
||||
this.audioFiles = orderedFileData.map((fileData) => {
|
||||
var audioFile = this.audioFiles.find(af => af.ino === fileData.ino)
|
||||
audioFile.manuallyVerified = true
|
||||
audioFile.invalid = false
|
||||
audioFile.error = null
|
||||
if (fileData.exclude !== undefined) {
|
||||
audioFile.exclude = !!fileData.exclude
|
||||
}
|
||||
if (audioFile.exclude) {
|
||||
audioFile.index = -1
|
||||
} else {
|
||||
audioFile.index = index++
|
||||
}
|
||||
return audioFile
|
||||
})
|
||||
return longest
|
||||
|
||||
this.rebuildTracks()
|
||||
}
|
||||
getTotalAudioTracks() {
|
||||
var total = 0
|
||||
this.audiobooks.forEach((ab) => total += ab.tracks.length)
|
||||
return total
|
||||
|
||||
rebuildTracks() {
|
||||
this.audioFiles.sort((a, b) => a.index - b.index)
|
||||
this.missingParts = []
|
||||
this.setChapters()
|
||||
this.checkUpdateMissingTracks()
|
||||
}
|
||||
getTotalDuration() {
|
||||
var total = 0
|
||||
this.audiobooks.forEach((ab) => total += ab.duration)
|
||||
return total
|
||||
|
||||
checkUpdateMissingTracks() {
|
||||
var currMissingParts = (this.missingParts || []).join(',') || ''
|
||||
|
||||
var current_index = 1
|
||||
var missingParts = []
|
||||
|
||||
for (let i = 0; i < this.tracks.length; i++) {
|
||||
var _track = this.tracks[i]
|
||||
if (_track.index > current_index) {
|
||||
var num_parts_missing = _track.index - current_index
|
||||
for (let x = 0; x < num_parts_missing && x < 9999; x++) {
|
||||
missingParts.push(current_index + x)
|
||||
}
|
||||
}
|
||||
current_index = _track.index + 1
|
||||
}
|
||||
|
||||
this.missingParts = missingParts
|
||||
|
||||
var newMissingParts = (this.missingParts || []).join(',') || ''
|
||||
var wasUpdated = newMissingParts !== currMissingParts
|
||||
if (wasUpdated && this.missingParts.length) {
|
||||
Logger.info(`[Audiobook] "${this.name}" has ${missingParts.length} missing parts`)
|
||||
}
|
||||
|
||||
return wasUpdated
|
||||
}
|
||||
|
||||
setChapters() {
|
||||
// If 1 audio file without chapters, then no chapters will be set
|
||||
var includedAudioFiles = this.audioFiles.filter(af => !af.exclude)
|
||||
if (includedAudioFiles.length === 1) {
|
||||
// 1 audio file with chapters
|
||||
if (includedAudioFiles[0].chapters) {
|
||||
this.chapters = includedAudioFiles[0].chapters.map(c => ({ ...c }))
|
||||
}
|
||||
} else {
|
||||
this.chapters = []
|
||||
var currChapterId = 0
|
||||
var currStartTime = 0
|
||||
includedAudioFiles.forEach((file) => {
|
||||
// If audio file has chapters use chapters
|
||||
if (file.chapters && file.chapters.length) {
|
||||
file.chapters.forEach((chapter) => {
|
||||
var chapterDuration = chapter.end - chapter.start
|
||||
if (chapterDuration > 0) {
|
||||
var title = `Chapter ${currChapterId}`
|
||||
if (chapter.title) {
|
||||
title += ` (${chapter.title})`
|
||||
}
|
||||
this.chapters.push({
|
||||
id: currChapterId++,
|
||||
start: currStartTime,
|
||||
end: currStartTime + chapterDuration,
|
||||
title
|
||||
})
|
||||
currStartTime += chapterDuration
|
||||
}
|
||||
})
|
||||
} else if (file.duration) {
|
||||
// Otherwise just use track has chapter
|
||||
this.chapters.push({
|
||||
id: currChapterId++,
|
||||
start: currStartTime,
|
||||
end: currStartTime + file.duration,
|
||||
title: file.metadata.filename ? Path.basename(file.metadata.filename, Path.extname(file.metadata.filename)) : `Chapter ${currChapterId}`
|
||||
})
|
||||
currStartTime += file.duration
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Only checks container format
|
||||
checkCanDirectPlay(payload) {
|
||||
var supportedMimeTypes = payload.supportedMimeTypes || []
|
||||
return !this.tracks.some((t) => !supportedMimeTypes.includes(t.mimeType))
|
||||
}
|
||||
|
||||
getDirectPlayTracklist(libraryItemId) {
|
||||
var tracklist = []
|
||||
|
||||
var startOffset = 0
|
||||
this.tracks.forEach((audioFile) => {
|
||||
var audioTrack = new AudioTrack()
|
||||
audioTrack.setData(libraryItemId, audioFile, startOffset)
|
||||
startOffset += audioTrack.duration
|
||||
tracklist.push(audioTrack)
|
||||
})
|
||||
|
||||
return tracklist
|
||||
}
|
||||
}
|
||||
module.exports = Book
|
||||
|
|
@ -74,6 +74,14 @@ class Podcast {
|
|||
get hasIssues() {
|
||||
return false
|
||||
}
|
||||
get duration() {
|
||||
var total = 0
|
||||
this.episodes.forEach((ep) => total += ep.duration)
|
||||
return total
|
||||
}
|
||||
get numTracks() {
|
||||
return this.episodes.length
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
var json = this.toJSON()
|
||||
|
|
@ -110,14 +118,6 @@ class Podcast {
|
|||
return null
|
||||
}
|
||||
|
||||
getMediaEntityById(entityId) {
|
||||
return this.episodes.find(ep => ep.id === entityId)
|
||||
}
|
||||
getPlaybackMediaEntity() { // Get first playback media entity
|
||||
if (!this.episodes.length) return null
|
||||
return this.episodes[0]
|
||||
}
|
||||
|
||||
setData(mediaMetadata) {
|
||||
this.metadata = new PodcastMetadata()
|
||||
if (mediaMetadata.metadata) {
|
||||
|
|
@ -137,26 +137,19 @@ class Podcast {
|
|||
return payload || {}
|
||||
}
|
||||
|
||||
getLongestDuration() {
|
||||
if (!this.episodes.length) return 0
|
||||
var longest = 0
|
||||
this.episodes.forEach((ab) => {
|
||||
if (ab.duration > longest) longest = ab.duration
|
||||
})
|
||||
return longest
|
||||
}
|
||||
|
||||
getTotalAudioTracks() {
|
||||
return this.episodes.length
|
||||
}
|
||||
getTotalDuration() {
|
||||
var total = 0
|
||||
this.episodes.forEach((ep) => total += ep.duration)
|
||||
return total
|
||||
}
|
||||
|
||||
addPodcastEpisode(podcastEpisode) {
|
||||
this.episodes.push(podcastEpisode)
|
||||
}
|
||||
|
||||
// Only checks container format
|
||||
checkCanDirectPlay(payload, epsiodeIndex = 0) {
|
||||
var episode = this.episodes[epsiodeIndex]
|
||||
return episode.checkCanDirectPlay(payload)
|
||||
}
|
||||
|
||||
getDirectPlayTracklist(libraryItemId, episodeIndex = 0) {
|
||||
var episode = this.episodes[episodeIndex]
|
||||
return episode.getDirectPlayTracklist(libraryItemId)
|
||||
}
|
||||
}
|
||||
module.exports = Podcast
|
||||
|
|
@ -77,7 +77,8 @@ class BookMetadata {
|
|||
explicit: this.explicit,
|
||||
authorName: this.authorName,
|
||||
authorNameLF: this.authorNameLF,
|
||||
narratorName: this.narratorName
|
||||
narratorName: this.narratorName,
|
||||
seriesName: this.seriesName
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
const Logger = require('../../Logger')
|
||||
|
||||
class LibraryItemProgress {
|
||||
class MediaProgress {
|
||||
constructor(progress) {
|
||||
this.id = null // Same as library item id
|
||||
this.libraryItemId = null
|
||||
this.mediaEntityId = null
|
||||
this.episodeId = null // For podcasts
|
||||
|
||||
this.duration = null
|
||||
this.progress = null // 0 to 1
|
||||
|
|
@ -24,7 +24,7 @@ class LibraryItemProgress {
|
|||
return {
|
||||
id: this.id,
|
||||
libraryItemId: this.libraryItemId,
|
||||
mediaEntityId: this.mediaEntityId,
|
||||
episodeId: this.episodeId,
|
||||
duration: this.duration,
|
||||
progress: this.progress,
|
||||
currentTime: this.currentTime,
|
||||
|
|
@ -38,7 +38,7 @@ class LibraryItemProgress {
|
|||
construct(progress) {
|
||||
this.id = progress.id
|
||||
this.libraryItemId = progress.libraryItemId
|
||||
this.mediaEntityId = progress.mediaEntityId || null
|
||||
this.episodeId = progress.episodeId
|
||||
this.duration = progress.duration || 0
|
||||
this.progress = progress.progress
|
||||
this.currentTime = progress.currentTime
|
||||
|
|
@ -52,10 +52,10 @@ class LibraryItemProgress {
|
|||
return !this.isFinished && this.progress > 0
|
||||
}
|
||||
|
||||
setData(libraryItemId, mediaEntityId, progress) {
|
||||
setData(libraryItemId, progress) {
|
||||
this.id = libraryItemId
|
||||
this.libraryItemId = libraryItemId
|
||||
this.mediaEntityId = mediaEntityId
|
||||
this.episodeId = progress.episodeId || null
|
||||
this.duration = progress.duration || 0
|
||||
this.progress = Math.min(1, (progress.progress || 0))
|
||||
this.currentTime = progress.currentTime || 0
|
||||
|
|
@ -97,4 +97,4 @@ class LibraryItemProgress {
|
|||
return hasUpdates
|
||||
}
|
||||
}
|
||||
module.exports = LibraryItemProgress
|
||||
module.exports = MediaProgress
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
const Logger = require('../../Logger')
|
||||
const AudioBookmark = require('./AudioBookmark')
|
||||
const LibraryItemProgress = require('./LibraryItemProgress')
|
||||
const MediaProgress = require('./MediaProgress')
|
||||
|
||||
class User {
|
||||
constructor(user) {
|
||||
|
|
@ -14,7 +14,7 @@ class User {
|
|||
this.lastSeen = null
|
||||
this.createdAt = null
|
||||
|
||||
this.libraryItemProgress = []
|
||||
this.mediaProgress = []
|
||||
this.bookmarks = []
|
||||
|
||||
this.settings = {}
|
||||
|
|
@ -84,7 +84,7 @@ class User {
|
|||
pash: this.pash,
|
||||
type: this.type,
|
||||
token: this.token,
|
||||
libraryItemProgress: this.libraryItemProgress ? this.libraryItemProgress.map(li => li.toJSON()) : [],
|
||||
mediaProgress: this.mediaProgress ? this.mediaProgress.map(li => li.toJSON()) : [],
|
||||
bookmarks: this.bookmarks ? this.bookmarks.map(b => b.toJSON()) : [],
|
||||
isActive: this.isActive,
|
||||
isLocked: this.isLocked,
|
||||
|
|
@ -103,7 +103,7 @@ class User {
|
|||
username: this.username,
|
||||
type: this.type,
|
||||
token: this.token,
|
||||
libraryItemProgress: this.libraryItemProgress ? this.libraryItemProgress.map(li => li.toJSON()) : [],
|
||||
mediaProgress: this.mediaProgress ? this.mediaProgress.map(li => li.toJSON()) : [],
|
||||
bookmarks: this.bookmarks ? this.bookmarks.map(b => b.toJSON()) : [],
|
||||
isActive: this.isActive,
|
||||
isLocked: this.isLocked,
|
||||
|
|
@ -118,12 +118,19 @@ class User {
|
|||
|
||||
// Data broadcasted
|
||||
toJSONForPublic(sessions, libraryItems) {
|
||||
var session = sessions ? sessions.find(s => s.userId === this.id) : null
|
||||
var userSession = sessions ? sessions.find(s => s.userId === this.id) : null
|
||||
var session = null
|
||||
if (session) {
|
||||
var libraryItem = libraryItems.find(li => li.id === session.libraryItemId)
|
||||
if (libraryItem) {
|
||||
session = userSession.toJSONForClient(libraryItem)
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: this.id,
|
||||
username: this.username,
|
||||
type: this.type,
|
||||
session: session ? session.toJSONForClient() : null,
|
||||
session,
|
||||
mostRecent: this.getMostRecentItemProgress(libraryItems),
|
||||
lastSeen: this.lastSeen,
|
||||
createdAt: this.createdAt
|
||||
|
|
@ -137,9 +144,9 @@ class User {
|
|||
this.type = user.type
|
||||
this.token = user.token
|
||||
|
||||
this.libraryItemProgress = []
|
||||
if (user.libraryItemProgress) {
|
||||
this.libraryItemProgress = user.libraryItemProgress.map(li => new LibraryItemProgress(li)).filter(lip => lip.id)
|
||||
this.mediaProgress = []
|
||||
if (user.mediaProgress) {
|
||||
this.mediaProgress = user.mediaProgress.map(li => new MediaProgress(li)).filter(lip => lip.id)
|
||||
}
|
||||
|
||||
this.bookmarks = []
|
||||
|
|
@ -217,8 +224,8 @@ class User {
|
|||
}
|
||||
|
||||
getMostRecentItemProgress(libraryItems) {
|
||||
if (!this.libraryItemProgress.length) return null
|
||||
var lip = this.libraryItemProgress.map(lip => lip.toJSON())
|
||||
if (!this.mediaProgress.length) return null
|
||||
var lip = this.mediaProgress.map(lip => lip.toJSON())
|
||||
lip.sort((a, b) => b.lastUpdate - a.lastUpdate)
|
||||
var mostRecentWithLip = lip.find(li => libraryItems.find(_li => _li.id === li.id))
|
||||
if (!mostRecentWithLip) return null
|
||||
|
|
@ -229,35 +236,27 @@ class User {
|
|||
}
|
||||
}
|
||||
|
||||
getLibraryItemProgress(libraryItemId) {
|
||||
if (!this.libraryItemProgress) return null
|
||||
return this.libraryItemProgress.find(lip => lip.id === libraryItemId)
|
||||
getMediaProgress(libraryItemId) {
|
||||
if (!this.mediaProgress) return null
|
||||
return this.mediaProgress.find(lip => lip.id === libraryItemId)
|
||||
}
|
||||
|
||||
createUpdateLibraryItemProgress(libraryItem, updatePayload) {
|
||||
var itemProgress = this.libraryItemProgress.find(li => li.id === libraryItem.id)
|
||||
createUpdateMediaProgress(libraryItem, updatePayload) {
|
||||
var itemProgress = this.mediaProgress.find(li => li.id === libraryItem.id)
|
||||
if (!itemProgress) {
|
||||
var newItemProgress = new LibraryItemProgress()
|
||||
var newItemProgress = new MediaProgress()
|
||||
|
||||
var mediaEntity = null
|
||||
if (updatePayload.mediaEntityId) mediaEntity = libraryItem.media.getMediaEntityById(updatePayload.mediaEntityId)
|
||||
if (!mediaEntity) mediaEntity = libraryItem.media.getPlaybackMediaEntity()
|
||||
if (!mediaEntity) {
|
||||
Logger.error(`[User] createUpdateLibraryItemProgress invalid library item has no playback media entity "${libraryItem.id}"`)
|
||||
return false
|
||||
}
|
||||
|
||||
newItemProgress.setData(libraryItem.id, mediaEntity.id, updatePayload)
|
||||
this.libraryItemProgress.push(newItemProgress)
|
||||
newItemProgress.setData(libraryItem.id, updatePayload)
|
||||
this.mediaProgress.push(newItemProgress)
|
||||
return true
|
||||
}
|
||||
var wasUpdated = itemProgress.update(updatePayload)
|
||||
return wasUpdated
|
||||
}
|
||||
|
||||
removeLibraryItemProgress(libraryItemId) {
|
||||
if (!this.libraryItemProgress.some(lip => lip.id == libraryItemId)) return false
|
||||
this.libraryItemProgress = this.libraryItemProgress.filter(lip => lip.id != libraryItemId)
|
||||
removeMediaProgress(libraryItemId) {
|
||||
if (!this.mediaProgress.some(lip => lip.id == libraryItemId)) return false
|
||||
this.mediaProgress = this.mediaProgress.filter(lip => lip.id != libraryItemId)
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -329,30 +328,31 @@ class User {
|
|||
this.bookmarks = this.bookmarks.filter(bm => (bm.libraryItemId !== libraryItemId || bm.time !== time))
|
||||
}
|
||||
|
||||
// TODO: re-do mobile sync
|
||||
syncLocalUserAudiobookData(localUserAudiobookData, audiobook) {
|
||||
if (!localUserAudiobookData || !localUserAudiobookData.audiobookId) {
|
||||
Logger.error(`[User] Invalid local user audiobook data`, localUserAudiobookData)
|
||||
return false
|
||||
}
|
||||
if (!this.audiobooks) this.audiobooks = {}
|
||||
// if (!localUserAudiobookData || !localUserAudiobookData.audiobookId) {
|
||||
// Logger.error(`[User] Invalid local user audiobook data`, localUserAudiobookData)
|
||||
// return false
|
||||
// }
|
||||
// if (!this.audiobooks) this.audiobooks = {}
|
||||
|
||||
if (!this.audiobooks[localUserAudiobookData.audiobookId]) {
|
||||
this.audiobooks[localUserAudiobookData.audiobookId] = new UserAudiobookData(localUserAudiobookData)
|
||||
return true
|
||||
}
|
||||
// if (!this.audiobooks[localUserAudiobookData.audiobookId]) {
|
||||
// this.audiobooks[localUserAudiobookData.audiobookId] = new UserAudiobookData(localUserAudiobookData)
|
||||
// return true
|
||||
// }
|
||||
|
||||
var userAbD = this.audiobooks[localUserAudiobookData.audiobookId]
|
||||
if (userAbD.lastUpdate >= localUserAudiobookData.lastUpdate) {
|
||||
// Server audiobook data is more recent
|
||||
return false
|
||||
}
|
||||
// var userAbD = this.audiobooks[localUserAudiobookData.audiobookId]
|
||||
// if (userAbD.lastUpdate >= localUserAudiobookData.lastUpdate) {
|
||||
// // Server audiobook data is more recent
|
||||
// return false
|
||||
// }
|
||||
|
||||
// Local Data More recent
|
||||
var wasUpdated = this.audiobooks[localUserAudiobookData.audiobookId].update(localUserAudiobookData)
|
||||
if (wasUpdated) {
|
||||
Logger.debug(`[User] syncLocalUserAudiobookData local data was more recent for "${audiobook.title}"`)
|
||||
}
|
||||
return wasUpdated
|
||||
// // Local Data More recent
|
||||
// var wasUpdated = this.audiobooks[localUserAudiobookData.audiobookId].update(localUserAudiobookData)
|
||||
// if (wasUpdated) {
|
||||
// Logger.debug(`[User] syncLocalUserAudiobookData local data was more recent for "${audiobook.title}"`)
|
||||
// }
|
||||
// return wasUpdated
|
||||
}
|
||||
}
|
||||
module.exports = User
|
||||
Loading…
Add table
Add a link
Reference in a new issue