mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
New data model change of Book media type to include array of Audiobook and Ebook objects
This commit is contained in:
parent
0af6ad63c1
commit
4fe60465e5
@ -1,168 +0,0 @@
|
||||
/*
|
||||
This is an example of a fully expanded book library item
|
||||
*/
|
||||
|
||||
const LibraryItem = require('../server/objects/LibraryItem')
|
||||
|
||||
new LibraryItem({
|
||||
id: 'li_abai123wir',
|
||||
ino: "55450570412017066",
|
||||
libraryId: 'lib_1239p1d8',
|
||||
folderId: 'fol_192ab8901',
|
||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule',
|
||||
relPath: '/Terry Goodkind/Sword of Truth/1 - Wizards First Rule',
|
||||
mtimeMs: 1646784672127,
|
||||
ctimeMs: 1646784672127,
|
||||
birthtimeMs: 1646784672127,
|
||||
addedAt: 1646784672127,
|
||||
lastUpdate: 1646784672127,
|
||||
lastScan: 1646784672127,
|
||||
scanVersion: 1.72,
|
||||
isMissing: false,
|
||||
mediaType: 'book',
|
||||
media: { // Book.js
|
||||
coverPath: '/metadata/books/li_abai123wir/cover.webp',
|
||||
metadata: { // BookMetadata.js
|
||||
title: 'Wizards First Rule',
|
||||
subtitle: null,
|
||||
authors: [
|
||||
{
|
||||
id: 'au_42908lkajsfdk',
|
||||
name: 'Terry Goodkind'
|
||||
}
|
||||
],
|
||||
narrators: ['Sam Tsoutsouvas'],
|
||||
series: [
|
||||
{
|
||||
id: 'se_902384lansf',
|
||||
name: 'Sword of Truth',
|
||||
sequence: 1
|
||||
}
|
||||
],
|
||||
genres: ['Fantasy', 'Adventure'],
|
||||
publishedYear: '1994',
|
||||
publishedDate: '1994-01-01',
|
||||
publisher: 'Brilliance Audio',
|
||||
description: 'In the aftermath of the brutal murder of his father, a mysterious woman...',
|
||||
isbn: '289374092834',
|
||||
asin: '19023819203',
|
||||
language: 'english'
|
||||
},
|
||||
tags: ['favorites'],
|
||||
audioFiles: [
|
||||
{ // AudioFile.js
|
||||
ino: "55450570412017066",
|
||||
index: 1,
|
||||
metadata: { // FileMetadata.js
|
||||
filename: 'audiofile.mp3',
|
||||
ext: '.mp3',
|
||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule/CD01/audiofile.mp3',
|
||||
relPath: '/CD01/audiofile.mp3',
|
||||
mtimeMs: 1646784672127,
|
||||
ctimeMs: 1646784672127,
|
||||
birthtimeMs: 1646784672127,
|
||||
size: 1197449516
|
||||
},
|
||||
trackNumFromMeta: 1,
|
||||
discNumFromMeta: null,
|
||||
trackNumFromFilename: null,
|
||||
discNumFromFilename: 1,
|
||||
manuallyVerified: false,
|
||||
exclude: false,
|
||||
invalid: false,
|
||||
format: "MP2/3 (MPEG audio layer 2/3)",
|
||||
duration: 2342342,
|
||||
bitRate: 324234,
|
||||
language: null,
|
||||
codec: 'mp3',
|
||||
timeBase: "1/14112000",
|
||||
channels: 1,
|
||||
channelLayout: "mono",
|
||||
chapters: [],
|
||||
embeddedCoverArt: 'jpeg', // Video stream codec ['mjpeg', 'jpeg', 'png'] or null
|
||||
metaTags: { // AudioMetaTags.js
|
||||
tagAlbum: '',
|
||||
tagArtist: '',
|
||||
tagGenre: '',
|
||||
tagTitle: '',
|
||||
tagSeries: '',
|
||||
tagSeriesPart: '',
|
||||
tagTrack: '',
|
||||
tagDisc: '',
|
||||
tagSubtitle: '',
|
||||
tagAlbumArtist: '',
|
||||
tagDate: '',
|
||||
tagComposer: '',
|
||||
tagPublisher: '',
|
||||
tagComment: '',
|
||||
tagDescription: '',
|
||||
tagEncoder: '',
|
||||
tagEncodedBy: '',
|
||||
tagIsbn: '',
|
||||
tagLanguage: '',
|
||||
tagASIN: ''
|
||||
},
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127
|
||||
}
|
||||
],
|
||||
ebookFiles: [
|
||||
{ // EBookFile.js
|
||||
ino: "55450570412017066",
|
||||
metadata: { // FileMetadata.js
|
||||
filename: 'ebookfile.mobi',
|
||||
ext: '.mobi',
|
||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule/ebookfile.mobi',
|
||||
relPath: '/ebookfile.mobi',
|
||||
mtimeMs: 1646784672127,
|
||||
ctimeMs: 1646784672127,
|
||||
birthtimeMs: 1646784672127,
|
||||
size: 1197449516
|
||||
},
|
||||
ebookFormat: 'mobi',
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127
|
||||
}
|
||||
],
|
||||
chapters: [
|
||||
{
|
||||
id: 0,
|
||||
title: 'Chapter 01',
|
||||
start: 0,
|
||||
end: 2467.753
|
||||
}
|
||||
]
|
||||
},
|
||||
libraryFiles: [
|
||||
{ // LibraryFile.js
|
||||
ino: "55450570412017066",
|
||||
metadata: { // FileMetadata.js
|
||||
filename: 'cover.png',
|
||||
ext: '.png',
|
||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule/subfolder/cover.png',
|
||||
relPath: '/subfolder/cover.png',
|
||||
mtimeMs: 1646784672127,
|
||||
ctimeMs: 1646784672127,
|
||||
birthtimeMs: 1646784672127,
|
||||
size: 1197449516
|
||||
},
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127
|
||||
},
|
||||
{ // LibraryFile.js
|
||||
ino: "55450570412017066",
|
||||
metadata: { // FileMetadata.js
|
||||
filename: 'cover.png',
|
||||
ext: '.mobi',
|
||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule/ebookfile.mobi',
|
||||
relPath: '/ebookfile.mobi',
|
||||
mtimeMs: 1646784672127,
|
||||
ctimeMs: 1646784672127,
|
||||
birthtimeMs: 1646784672127,
|
||||
size: 1197449516
|
||||
},
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127
|
||||
}
|
||||
]
|
||||
})
|
189
docs/SampleBookLibraryItem.js
Normal file
189
docs/SampleBookLibraryItem.js
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
This is an example of a fully expanded book library item
|
||||
*/
|
||||
|
||||
const LibraryItem = require('../server/objects/LibraryItem')
|
||||
|
||||
new LibraryItem({
|
||||
id: 'li_abai123wir',
|
||||
ino: "55450570412017066",
|
||||
libraryId: 'lib_1239p1d8',
|
||||
folderId: 'fol_192ab8901',
|
||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule',
|
||||
relPath: '/Terry Goodkind/Sword of Truth/1 - Wizards First Rule',
|
||||
mtimeMs: 1646784672127,
|
||||
ctimeMs: 1646784672127,
|
||||
birthtimeMs: 1646784672127,
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127,
|
||||
lastScan: 1646784672127,
|
||||
scanVersion: 1.72,
|
||||
isMissing: false,
|
||||
isInvalid: false,
|
||||
mediaType: 'book',
|
||||
media: { // Book.js
|
||||
coverPath: '/metadata/items/li_abai123wir/cover.webp',
|
||||
tags: ['favorites'],
|
||||
lastCoverSearch: null,
|
||||
lastCoverSearchQuery: null,
|
||||
metadata: { // BookMetadata.js
|
||||
title: 'Wizards First Rule',
|
||||
subtitle: null,
|
||||
authors: [
|
||||
{
|
||||
id: 'au_42908lkajsfdk',
|
||||
name: 'Terry Goodkind'
|
||||
}
|
||||
],
|
||||
narrators: ['Sam Tsoutsouvas'],
|
||||
series: [
|
||||
{
|
||||
id: 'se_902384lansf',
|
||||
name: 'Sword of Truth',
|
||||
sequence: 1
|
||||
}
|
||||
],
|
||||
genres: ['Fantasy', 'Adventure'],
|
||||
publishedYear: '1994',
|
||||
publishedDate: '1994-01-01',
|
||||
publisher: 'Brilliance Audio',
|
||||
description: 'In the aftermath of the brutal murder of his father, a mysterious woman...',
|
||||
isbn: '289374092834',
|
||||
asin: '19023819203',
|
||||
language: 'english',
|
||||
explicit: false
|
||||
},
|
||||
audiobooks: [
|
||||
{
|
||||
id: 'au_289374asf0a98',
|
||||
index: 1,
|
||||
name: 'default',
|
||||
audioFiles: [
|
||||
{ // AudioFile.js
|
||||
ino: "55450570412017066",
|
||||
index: 1,
|
||||
metadata: { // FileMetadata.js
|
||||
filename: 'audiofile.mp3',
|
||||
ext: '.mp3',
|
||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule/CD01/audiofile.mp3',
|
||||
relPath: '/CD01/audiofile.mp3',
|
||||
mtimeMs: 1646784672127,
|
||||
ctimeMs: 1646784672127,
|
||||
birthtimeMs: 1646784672127,
|
||||
size: 1197449516
|
||||
},
|
||||
trackNumFromMeta: 1,
|
||||
discNumFromMeta: null,
|
||||
trackNumFromFilename: null,
|
||||
discNumFromFilename: 1,
|
||||
manuallyVerified: false,
|
||||
exclude: false,
|
||||
invalid: false,
|
||||
format: "MP2/3 (MPEG audio layer 2/3)",
|
||||
duration: 2342342,
|
||||
bitRate: 324234,
|
||||
language: null,
|
||||
codec: 'mp3',
|
||||
timeBase: "1/14112000",
|
||||
channels: 1,
|
||||
channelLayout: "mono",
|
||||
chapters: [],
|
||||
embeddedCoverArt: 'jpeg', // Video stream codec ['mjpeg', 'jpeg', 'png'] or null
|
||||
metaTags: { // AudioMetaTags.js
|
||||
tagAlbum: '',
|
||||
tagArtist: '',
|
||||
tagGenre: '',
|
||||
tagTitle: '',
|
||||
tagSeries: '',
|
||||
tagSeriesPart: '',
|
||||
tagTrack: '',
|
||||
tagDisc: '',
|
||||
tagSubtitle: '',
|
||||
tagAlbumArtist: '',
|
||||
tagDate: '',
|
||||
tagComposer: '',
|
||||
tagPublisher: '',
|
||||
tagComment: '',
|
||||
tagDescription: '',
|
||||
tagEncoder: '',
|
||||
tagEncodedBy: '',
|
||||
tagIsbn: '',
|
||||
tagLanguage: '',
|
||||
tagASIN: ''
|
||||
},
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127
|
||||
}
|
||||
],
|
||||
chapters: [
|
||||
{
|
||||
id: 0,
|
||||
title: 'Chapter 01',
|
||||
start: 0,
|
||||
end: 2467.753
|
||||
}
|
||||
],
|
||||
missingParts: [4, 10], // Array of missing parts in tracklist
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127
|
||||
}
|
||||
],
|
||||
ebooks: [
|
||||
{
|
||||
id: 'eb_289374asf0a98',
|
||||
index: 1,
|
||||
name: 'default',
|
||||
ebookFile: { // EBookFile.js
|
||||
ino: "55450570412017066",
|
||||
metadata: { // FileMetadata.js
|
||||
filename: 'ebookfile.mobi',
|
||||
ext: '.mobi',
|
||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule/ebookfile.mobi',
|
||||
relPath: '/ebookfile.mobi',
|
||||
mtimeMs: 1646784672127,
|
||||
ctimeMs: 1646784672127,
|
||||
birthtimeMs: 1646784672127,
|
||||
size: 1197449516
|
||||
},
|
||||
ebookFormat: 'mobi',
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127
|
||||
},
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127
|
||||
}
|
||||
]
|
||||
},
|
||||
libraryFiles: [
|
||||
{ // LibraryFile.js
|
||||
ino: "55450570412017066",
|
||||
metadata: { // FileMetadata.js
|
||||
filename: 'cover.png',
|
||||
ext: '.png',
|
||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule/subfolder/cover.png',
|
||||
relPath: '/subfolder/cover.png',
|
||||
mtimeMs: 1646784672127,
|
||||
ctimeMs: 1646784672127,
|
||||
birthtimeMs: 1646784672127,
|
||||
size: 1197449516
|
||||
},
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127
|
||||
},
|
||||
{ // LibraryFile.js
|
||||
ino: "55450570412017066",
|
||||
metadata: { // FileMetadata.js
|
||||
filename: 'cover.png',
|
||||
ext: '.mobi',
|
||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule/ebookfile.mobi',
|
||||
relPath: '/ebookfile.mobi',
|
||||
mtimeMs: 1646784672127,
|
||||
ctimeMs: 1646784672127,
|
||||
birthtimeMs: 1646784672127,
|
||||
size: 1197449516
|
||||
},
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127
|
||||
}
|
||||
]
|
||||
})
|
83
docs/SamplePodcastLibraryItem.js
Normal file
83
docs/SamplePodcastLibraryItem.js
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
This is an example of a fully expanded podcast library item (under construction)
|
||||
*/
|
||||
|
||||
const LibraryItem = require('../server/objects/LibraryItem')
|
||||
|
||||
new LibraryItem({
|
||||
id: 'li_abai123wir',
|
||||
ino: "55450570412017066",
|
||||
libraryId: 'lib_1239p1d8',
|
||||
folderId: 'fol_192ab8901',
|
||||
path: '/podcasts/Great Podcast Name',
|
||||
relPath: '/Great Podcast Name',
|
||||
mtimeMs: 1646784672127,
|
||||
ctimeMs: 1646784672127,
|
||||
birthtimeMs: 1646784672127,
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127,
|
||||
lastScan: 1646784672127,
|
||||
scanVersion: 1.72,
|
||||
isMissing: false,
|
||||
isInvalid: false,
|
||||
mediaType: 'podcast',
|
||||
media: { // Podcast.js
|
||||
coverPath: '/metadata/items/li_abai123wir/cover.webp',
|
||||
tags: ['favorites'],
|
||||
lastCoverSearch: null,
|
||||
lastCoverSearchQuery: null,
|
||||
metadata: { // PodcastMetadata.js
|
||||
title: 'Great Podcast Name',
|
||||
artist: 'Some Artist Name',
|
||||
genres: ['Fantasy', 'Adventure'],
|
||||
publishedDate: '1994-01-01',
|
||||
description: 'In the aftermath of the brutal murder of his father, a mysterious woman...',
|
||||
feedUrl: '',
|
||||
itunesPageUrl: '',
|
||||
itunesId: '',
|
||||
itunesArtistId: '',
|
||||
explicit: false
|
||||
},
|
||||
episodes: [
|
||||
{ // PodcastEpisode.js
|
||||
id: 'ep_289374asf0a98',
|
||||
index: 1,
|
||||
// TODO: podcast episode data and PodcastEpisodeMetadata
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127
|
||||
}
|
||||
]
|
||||
},
|
||||
libraryFiles: [
|
||||
{ // LibraryFile.js
|
||||
ino: "55450570412017066",
|
||||
metadata: { // FileMetadata.js
|
||||
filename: 'cover.png',
|
||||
ext: '.png',
|
||||
path: '/podcasts/Great Podcast Name/cover.png',
|
||||
relPath: '/cover.png',
|
||||
mtimeMs: 1646784672127,
|
||||
ctimeMs: 1646784672127,
|
||||
birthtimeMs: 1646784672127,
|
||||
size: 1197449516
|
||||
},
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127
|
||||
},
|
||||
{ // LibraryFile.js
|
||||
ino: "55450570412017066",
|
||||
metadata: { // FileMetadata.js
|
||||
filename: 'episode_1.mp3',
|
||||
ext: '.mp3',
|
||||
path: '/podcasts/Great Podcast Name/episode_1.mp3',
|
||||
relPath: '/episode_1.mp3',
|
||||
mtimeMs: 1646784672127,
|
||||
ctimeMs: 1646784672127,
|
||||
birthtimeMs: 1646784672127,
|
||||
size: 1197449516
|
||||
},
|
||||
addedAt: 1646784672127,
|
||||
updatedAt: 1646784672127
|
||||
}
|
||||
]
|
||||
})
|
@ -11,17 +11,23 @@ class PlaybackSessionManager {
|
||||
this.sessions = []
|
||||
}
|
||||
|
||||
startSessionRequest(req, res) {
|
||||
async startSessionRequest(req, res) {
|
||||
var user = req.user
|
||||
var libraryItem = req.libraryItem
|
||||
var options = req.query
|
||||
const session = this.startSession(user, libraryItem, options)
|
||||
var options = req.query || {}
|
||||
const session = await this.startSession(user, libraryItem, options)
|
||||
res.json(session)
|
||||
}
|
||||
|
||||
startSession(user, libraryItem, options) {
|
||||
async startSession(user, libraryItem, options) {
|
||||
// TODO: Determine what play method to use and setup playback session
|
||||
// temporary client can pass direct=1 in query string for direct play
|
||||
if (options.direct) {
|
||||
var tracks = libraryItem.media.getDirectPlayTracklist(options)
|
||||
}
|
||||
|
||||
const newPlaybackSession = new PlaybackSession()
|
||||
newPlaybackSession.setData(libraryItem, user)
|
||||
this.sessions.push(newPlaybackSession)
|
||||
return newPlaybackSession
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
const { version } = require('../../package.json')
|
||||
const Logger = require('../Logger')
|
||||
const LibraryFile = require('./files/LibraryFile')
|
||||
const Book = require('./entities/Book')
|
||||
const Podcast = require('./entities/Podcast')
|
||||
const Book = require('./mediaTypes/Book')
|
||||
const Podcast = require('./mediaTypes/Podcast')
|
||||
const { areEquivalent, copyValue, getId } = require('../utils/index')
|
||||
|
||||
class LibraryItem {
|
||||
@ -143,8 +143,8 @@ class LibraryItem {
|
||||
get hasAudioFiles() {
|
||||
return this.libraryFiles.some(lf => lf.fileType === 'audio')
|
||||
}
|
||||
get hasMediaFiles() {
|
||||
return this.media.hasMediaFiles
|
||||
get hasMediaEntities() {
|
||||
return this.media.hasMediaEntities
|
||||
}
|
||||
|
||||
// Data comes from scandir library item data
|
||||
@ -357,7 +357,7 @@ class LibraryItem {
|
||||
}
|
||||
|
||||
// Check if invalid
|
||||
this.isInvalid = !this.media.hasMediaFiles
|
||||
this.isInvalid = !this.media.hasMediaEntities
|
||||
|
||||
// If cover path is in item folder, make sure libraryFile exists for it
|
||||
if (this.media.coverPath && this.media.coverPath.startsWith(this.path)) {
|
||||
@ -432,5 +432,9 @@ class LibraryItem {
|
||||
query = query.toLowerCase()
|
||||
return this.media.searchQuery(query)
|
||||
}
|
||||
|
||||
getDirectPlayTracklist(options) {
|
||||
return this.media.getDirectPlayTracklist(options)
|
||||
}
|
||||
}
|
||||
module.exports = LibraryItem
|
218
server/objects/entities/Audiobook.js
Normal file
218
server/objects/entities/Audiobook.js
Normal file
@ -0,0 +1,218 @@
|
||||
const Path = require('path')
|
||||
const AudioFile = require('../files/AudioFile')
|
||||
const { areEquivalent, copyValue } = require('../../utils/index')
|
||||
|
||||
class Audiobook {
|
||||
constructor(audiobook) {
|
||||
this.id = null
|
||||
this.index = null
|
||||
this.name = null
|
||||
this.audioFiles = []
|
||||
this.chapters = []
|
||||
this.missingParts = []
|
||||
this.addedAt = null
|
||||
this.updatedAt = null
|
||||
|
||||
if (audiobook) {
|
||||
this.construct(audiobook)
|
||||
}
|
||||
}
|
||||
|
||||
construct(audiobook) {
|
||||
this.id = audiobook.id
|
||||
this.index = audiobook.index
|
||||
this.name = audiobook.name || null
|
||||
this.audioFiles = audiobook.audioFiles.map(f => new AudioFile(f))
|
||||
this.chapters = audiobook.chapters.map(c => ({ ...c }))
|
||||
this.missingParts = audiobook.missingParts ? [...book.missingParts] : []
|
||||
this.addedAt = audiobook.addedAt
|
||||
this.updatedAt = audiobook.updatedAt
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
index: this.index,
|
||||
name: this.name,
|
||||
audioFiles: this.audioFiles.map(f => f.toJSON()),
|
||||
chapters: this.chapters.map(c => ({ ...c })),
|
||||
missingParts: [...this.missingParts],
|
||||
addedAt: this.addedAt,
|
||||
updatedAt: this.updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
toJSONMinified() {
|
||||
return {
|
||||
id: this.id,
|
||||
index: this.index,
|
||||
name: this.name,
|
||||
numTracks: this.tracks.length,
|
||||
numAudioFiles: this.audioFiles.length,
|
||||
numChapters: this.chapters.length,
|
||||
numMissingParts: this.missingParts.length,
|
||||
duration: this.duration,
|
||||
size: this.size,
|
||||
addedAt: this.addedAt,
|
||||
updatedAt: this.updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
toJSONExpanded() {
|
||||
return {
|
||||
id: this.id,
|
||||
index: this.index,
|
||||
name: this.name,
|
||||
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],
|
||||
addedAt: this.addedAt,
|
||||
updatedAt: this.updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
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 size() {
|
||||
var total = 0
|
||||
this.audioFiles.forEach((af) => total += af.metadata.size)
|
||||
return total
|
||||
}
|
||||
get hasEmbeddedCoverArt() {
|
||||
return this.audioFiles.some(af => af.embeddedCoverArt)
|
||||
}
|
||||
|
||||
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('[Audiobook] Key updated', key, this[key])
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
this.rebuildTracks()
|
||||
}
|
||||
|
||||
rebuildTracks() {
|
||||
this.audioFiles.sort((a, b) => a.index - b.index)
|
||||
this.missingParts = []
|
||||
this.setChapters()
|
||||
this.checkUpdateMissingTracks()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
findFileWithInode(inode) {
|
||||
return this.audioFiles.find(af => af.ino === inode)
|
||||
}
|
||||
|
||||
removeFileWithInode(inode) {
|
||||
this.audioFiles = this.audioFiles.filter(af => af.ino !== inode)
|
||||
}
|
||||
}
|
||||
module.exports = Audiobook
|
70
server/objects/entities/EBook.js
Normal file
70
server/objects/entities/EBook.js
Normal file
@ -0,0 +1,70 @@
|
||||
const EBookFile = require('../files/EBookFile')
|
||||
const { areEquivalent, copyValue } = require('../../utils/index')
|
||||
|
||||
class EBook {
|
||||
constructor(ebook) {
|
||||
this.id = null
|
||||
this.index = null
|
||||
this.name = null
|
||||
this.ebookFile = null
|
||||
this.addedAt = null
|
||||
this.updatedAt = null
|
||||
|
||||
if (ebook) {
|
||||
this.construct(ebook)
|
||||
}
|
||||
}
|
||||
|
||||
construct(ebook) {
|
||||
this.id = ebook.id
|
||||
this.index = ebook.index
|
||||
this.name = ebook.name
|
||||
this.ebookFile = new EBookFile(ebook.ebookFile)
|
||||
this.addedAt = ebook.addedAt
|
||||
this.updatedAt = ebook.updatedAt
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
index: this.index,
|
||||
name: this.name,
|
||||
ebookFile: this.ebookFile.toJSON(),
|
||||
addedAt: this.addedAt,
|
||||
updatedAt: this.updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
toJSONMinified() {
|
||||
return {
|
||||
id: this.id,
|
||||
index: this.index,
|
||||
name: this.name,
|
||||
ebookFormat: this.ebookFile.ebookFormat,
|
||||
addedAt: this.addedAt,
|
||||
updatedAt: this.updatedAt,
|
||||
size: this.size
|
||||
}
|
||||
}
|
||||
|
||||
toJSONMinified() {
|
||||
return {
|
||||
id: this.id,
|
||||
index: this.index,
|
||||
name: this.name,
|
||||
ebookFile: this.ebookFile.toJSON(),
|
||||
addedAt: this.addedAt,
|
||||
updatedAt: this.updatedAt,
|
||||
size: this.size
|
||||
}
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.ebookFile.metadata.size
|
||||
}
|
||||
|
||||
findFileWithInode(inode) {
|
||||
return this.ebookFile.ino === inode
|
||||
}
|
||||
}
|
||||
module.exports = EBook
|
@ -3,6 +3,7 @@ const AudioFile = require('../files/AudioFile')
|
||||
class PodcastEpisode {
|
||||
constructor(episode) {
|
||||
this.id = null
|
||||
this.index = null
|
||||
this.podcastId = null
|
||||
this.episodeNumber = null
|
||||
|
||||
@ -17,6 +18,7 @@ class PodcastEpisode {
|
||||
|
||||
construct(episode) {
|
||||
this.id = episode.id
|
||||
this.index = episode.index
|
||||
this.podcastId = episode.podcastId
|
||||
this.episodeNumber = episode.episodeNumber
|
||||
this.audioFile = new AudioFile(episode.audioFile)
|
||||
@ -27,6 +29,7 @@ class PodcastEpisode {
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
index: this.index,
|
||||
podcastId: this.podcastId,
|
||||
episodeNumber: this.episodeNumber,
|
||||
audioFile: this.audioFile.toJSON(),
|
||||
|
@ -1,23 +1,23 @@
|
||||
const Path = require('path')
|
||||
const Logger = require('../../Logger')
|
||||
const BookMetadata = require('../metadata/BookMetadata')
|
||||
const AudioFile = require('../files/AudioFile')
|
||||
const EBookFile = require('../files/EBookFile')
|
||||
const abmetadataGenerator = require('../../utils/abmetadataGenerator')
|
||||
const { areEquivalent, copyValue } = require('../../utils/index')
|
||||
const { parseOpfMetadataXML } = require('../../utils/parseOpfMetadata')
|
||||
const { readTextFile } = require('../../utils/fileUtils')
|
||||
|
||||
const Audiobook = require('../entities/Audiobook')
|
||||
const EBook = require('../entities/EBook')
|
||||
|
||||
class Book {
|
||||
constructor(book) {
|
||||
this.metadata = null
|
||||
|
||||
this.coverPath = null
|
||||
this.tags = []
|
||||
this.audioFiles = []
|
||||
this.ebookFiles = []
|
||||
this.chapters = []
|
||||
this.missingParts = []
|
||||
|
||||
this.audiobooks = []
|
||||
this.ebooks = []
|
||||
|
||||
this.lastCoverSearch = null
|
||||
this.lastCoverSearchQuery = null
|
||||
@ -31,10 +31,8 @@ class Book {
|
||||
this.metadata = new BookMetadata(book.metadata)
|
||||
this.coverPath = book.coverPath
|
||||
this.tags = [...book.tags]
|
||||
this.audioFiles = book.audioFiles.map(f => new AudioFile(f))
|
||||
this.ebookFiles = book.ebookFiles.map(f => new EBookFile(f))
|
||||
this.chapters = book.chapters.map(c => ({ ...c }))
|
||||
this.missingParts = book.missingParts ? [...book.missingParts] : []
|
||||
this.audiobooks = book.audiobooks.map(ab => new Audiobook(ab))
|
||||
this.ebooks = book.ebooks.map(eb => new EBook(eb))
|
||||
this.lastCoverSearch = book.lastCoverSearch || null
|
||||
this.lastCoverSearchQuery = book.lastCoverSearchQuery || null
|
||||
}
|
||||
@ -44,10 +42,8 @@ class Book {
|
||||
metadata: this.metadata.toJSON(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
audioFiles: this.audioFiles.map(f => f.toJSON()),
|
||||
ebookFiles: this.ebookFiles.map(f => f.toJSON()),
|
||||
chapters: this.chapters.map(c => ({ ...c })),
|
||||
missingParts: [...this.missingParts]
|
||||
audiobooks: this.audiobooks.map(ab => ab.toJSON()),
|
||||
ebooks: this.ebooks.map(eb => eb.toJSON())
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,11 +52,8 @@ class Book {
|
||||
metadata: this.metadata.toJSON(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
numTracks: this.tracks.length,
|
||||
numAudioFiles: this.audioFiles.length,
|
||||
numEbooks: this.ebookFiles.length,
|
||||
numChapters: this.chapters.length,
|
||||
numMissingParts: this.missingParts.length,
|
||||
audiobooks: this.audiobooks.map(ab => ab.toJSONMinified()),
|
||||
ebooks: this.ebooks.map(eb => eb.toJSONMinified()),
|
||||
duration: this.duration,
|
||||
size: this.size
|
||||
}
|
||||
@ -71,13 +64,10 @@ class Book {
|
||||
metadata: this.metadata.toJSONExpanded(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
audioFiles: this.audioFiles.map(f => f.toJSON()),
|
||||
ebookFiles: this.ebookFiles.map(f => f.toJSON()),
|
||||
chapters: this.chapters.map(c => ({ ...c })),
|
||||
audiobooks: this.audiobooks.map(ab => ab.toJSONExpanded()),
|
||||
ebooks: this.ebooks.map(eb => eb.toJSONExpanded()),
|
||||
duration: this.duration,
|
||||
size: this.size,
|
||||
tracks: this.tracks.map(t => t.toJSON()),
|
||||
missingParts: [...this.missingParts]
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,8 +84,8 @@ class Book {
|
||||
this.audioFiles.forEach((af) => total += af.metadata.size)
|
||||
return total
|
||||
}
|
||||
get hasMediaFiles() {
|
||||
return !!(this.tracks.length + this.ebookFiles.length)
|
||||
get hasMediaEntities() {
|
||||
return !!(this.audiobooks.length + this.ebooks.length)
|
||||
}
|
||||
get shouldSearchForCover() {
|
||||
if (this.coverPath) return false
|
||||
@ -103,11 +93,14 @@ class Book {
|
||||
return (Date.now() - this.lastCoverSearch) > 1000 * 60 * 60 * 24 * 7 // 7 day
|
||||
}
|
||||
get hasEmbeddedCoverArt() {
|
||||
return this.audioFiles.some(af => af.embeddedCoverArt)
|
||||
return this.audiobooks.some(ab => ab.hasEmbeddedCoverArt)
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
var json = this.toJSON()
|
||||
delete json.audiobooks // do not update media entities here
|
||||
delete json.ebooks
|
||||
|
||||
var hasUpdates = false
|
||||
for (const key in json) {
|
||||
if (payload[key] !== undefined) {
|
||||
@ -125,27 +118,6 @@ class Book {
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
this.rebuildTracks()
|
||||
}
|
||||
|
||||
updateCover(coverPath) {
|
||||
coverPath = coverPath.replace(/\\/g, '/')
|
||||
if (this.coverPath === coverPath) return false
|
||||
@ -153,50 +125,27 @@ class Book {
|
||||
return true
|
||||
}
|
||||
|
||||
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(`[Book] "${this.metadata.title}" has ${missingParts.length} missing parts`)
|
||||
}
|
||||
|
||||
return wasUpdated
|
||||
}
|
||||
|
||||
removeFileWithInode(inode) {
|
||||
if (this.audioFiles.some(af => af.ino === inode)) {
|
||||
this.audioFiles = this.audioFiles.filter(af => af.ino !== 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)
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (this.ebookFiles.some(ef => ef.ino === inode)) {
|
||||
this.ebookFiles = this.ebookFiles.filter(ef => ef.ino !== inode)
|
||||
var ebookWithIno = this.ebooks.find(eb => eb.findFileWithInode(inode))
|
||||
if (ebookWithIno) {
|
||||
this.ebooks = this.ebooks.filter(eb => eb.id !== ebookWithIno.id) // Remove ebook
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
findFileWithInode(inode) {
|
||||
var audioFile = this.audioFiles.find(af => af.ino == inode)
|
||||
var audioFile = this.audiobooks.find(ab => ab.findFileWithInode(inode))
|
||||
if (audioFile) return audioFile
|
||||
var ebookFile = this.ebookFiles.find(ef => ef.inode == inode)
|
||||
var ebookFile = this.ebooks.find(eb => eb.findFileWithInode(inode))
|
||||
if (ebookFile) return ebookFile
|
||||
return null
|
||||
}
|
||||
@ -208,64 +157,13 @@ class Book {
|
||||
|
||||
// Audio file metadata tags map to book details (will not overwrite)
|
||||
setMetadataFromAudioFile(overrideExistingDetails = false) {
|
||||
if (!this.audioFiles.length) return false
|
||||
var audioFile = this.audioFiles[0]
|
||||
if (!this.audiobooks.length) return false
|
||||
var audiobook = this.audiobooks[0]
|
||||
var audioFile = audiobook.audioFiles[0]
|
||||
if (!audioFile.metaTags) return false
|
||||
return this.metadata.setDataFromAudioMetaTags(audioFile.metaTags, overrideExistingDetails)
|
||||
}
|
||||
|
||||
rebuildTracks() {
|
||||
this.audioFiles.sort((a, b) => a.index - b.index)
|
||||
this.missingParts = []
|
||||
this.setChapters()
|
||||
this.checkUpdateMissingTracks()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
setData(scanMediaMetadata) {
|
||||
this.metadata = new BookMetadata()
|
||||
this.metadata.setData(scanMediaMetadata)
|
||||
@ -365,9 +263,13 @@ class Book {
|
||||
}
|
||||
|
||||
addEbookFile(libraryFile) {
|
||||
var newEbook = new EBookFile()
|
||||
newEbook.setData(libraryFile)
|
||||
this.ebookFiles.push(newEbook)
|
||||
// var newEbook = new EBookFile()
|
||||
// newEbook.setData(libraryFile)
|
||||
// this.ebookFiles.push(newEbook)
|
||||
}
|
||||
|
||||
getDirectPlayTracklist(options) {
|
||||
|
||||
}
|
||||
}
|
||||
module.exports = Book
|
@ -1,4 +1,4 @@
|
||||
const PodcastEpisode = require('./PodcastEpisode')
|
||||
const PodcastEpisode = require('../entities/PodcastEpisode')
|
||||
const PodcastMetadata = require('../metadata/PodcastMetadata')
|
||||
const { areEquivalent, copyValue } = require('../../utils/index')
|
||||
|
||||
@ -68,7 +68,7 @@ class Podcast {
|
||||
get size() {
|
||||
return 0
|
||||
}
|
||||
get hasMediaFiles() {
|
||||
get hasMediaEntities() {
|
||||
return !!this.episodes.length
|
||||
}
|
||||
get shouldSearchForCover() {
|
||||
@ -80,6 +80,7 @@ class Podcast {
|
||||
|
||||
update(payload) {
|
||||
var json = this.toJSON()
|
||||
delete json.episodes // do not update media entities here
|
||||
var hasUpdates = false
|
||||
for (const key in json) {
|
||||
if (payload[key] !== undefined) {
|
||||
@ -129,5 +130,9 @@ class Podcast {
|
||||
var payload = this.metadata.searchQuery(query)
|
||||
return payload || {}
|
||||
}
|
||||
|
||||
getDirectPlayTracklist(options) {
|
||||
|
||||
}
|
||||
}
|
||||
module.exports = Podcast
|
@ -49,6 +49,9 @@ class FileMetadata {
|
||||
if (!this.ext) return ''
|
||||
return this.ext.slice(1)
|
||||
}
|
||||
get filenameNoExt() {
|
||||
return this.filename.replace(this.ext, '')
|
||||
}
|
||||
|
||||
update(payload) {
|
||||
var hasUpdates = false
|
||||
|
@ -109,7 +109,7 @@ class Scanner {
|
||||
}
|
||||
}
|
||||
|
||||
if (!libraryItem.hasMediaFiles) { // Library Item is invalid
|
||||
if (!libraryItem.hasMediaEntities) { // Library Item is invalid
|
||||
libraryItem.setInvalid()
|
||||
hasUpdated = true
|
||||
} else if (libraryItem.isInvalid) {
|
||||
@ -357,7 +357,7 @@ class Scanner {
|
||||
// Temp authors & series are inserted - create them if found
|
||||
await this.createNewAuthorsAndSeries(libraryItem)
|
||||
|
||||
if (!libraryItem.media.hasMediaFiles) { // Library item is invalid
|
||||
if (!libraryItem.hasMediaEntities) { // Library item is invalid
|
||||
libraryItem.setInvalid()
|
||||
hasUpdated = true
|
||||
} else if (libraryItem.isInvalid) {
|
||||
@ -387,7 +387,7 @@ class Scanner {
|
||||
await AudioFileScanner.scanAudioFiles(audioFiles, libraryItemData, libraryItem, preferAudioMetadata, libraryScan)
|
||||
}
|
||||
|
||||
if (!libraryItem.media.hasMediaFiles) {
|
||||
if (!libraryItem.hasMediaEntities) {
|
||||
Logger.warn(`[Scanner] Library item has no media files "${libraryItemData.path}"`)
|
||||
return null
|
||||
}
|
||||
|
@ -3,21 +3,27 @@ const fs = require('fs-extra')
|
||||
const njodb = require("njodb")
|
||||
|
||||
const { SupportedEbookTypes } = require('./globals')
|
||||
const Audiobook = require('../objects/legacy/Audiobook')
|
||||
const LegacyAudiobook = require('../objects/legacy/Audiobook')
|
||||
const UserAudiobookData = require('../objects/legacy/UserAudiobookData')
|
||||
|
||||
const LibraryItem = require('../objects/LibraryItem')
|
||||
|
||||
const Logger = require('../Logger')
|
||||
const Book = require('../objects/entities/Book')
|
||||
const Book = require('../objects/mediaTypes/Book')
|
||||
|
||||
const BookMetadata = require('../objects/metadata/BookMetadata')
|
||||
const Author = require('../objects/entities/Author')
|
||||
const Series = require('../objects/entities/Series')
|
||||
const FileMetadata = require('../objects/metadata/FileMetadata')
|
||||
|
||||
const AudioFile = require('../objects/files/AudioFile')
|
||||
const EBookFile = require('../objects/files/EBookFile')
|
||||
const LibraryFile = require('../objects/files/LibraryFile')
|
||||
const FileMetadata = require('../objects/metadata/FileMetadata')
|
||||
const AudioMetaTags = require('../objects/metadata/AudioMetaTags')
|
||||
|
||||
const Author = require('../objects/entities/Author')
|
||||
const Series = require('../objects/entities/Series')
|
||||
const Audiobook = require('../objects/entities/Audiobook')
|
||||
const EBook = require('../objects/entities/EBook')
|
||||
|
||||
const LibraryItemProgress = require('../objects/user/LibraryItemProgress')
|
||||
const PlaybackSession = require('../objects/PlaybackSession')
|
||||
|
||||
@ -40,7 +46,7 @@ async function loadAudiobooks() {
|
||||
|
||||
var audiobooksDb = new njodb.Database(audiobookPath)
|
||||
return audiobooksDb.select(() => true).then((results) => {
|
||||
return results.data.map(a => new Audiobook(a))
|
||||
return results.data.map(a => new LegacyAudiobook(a))
|
||||
})
|
||||
}
|
||||
|
||||
@ -144,7 +150,7 @@ function makeFilesFromOldAb(audiobook) {
|
||||
|
||||
function makeLibraryItemFromOldAb(audiobook) {
|
||||
var libraryItem = new LibraryItem()
|
||||
libraryItem.id = audiobook.id
|
||||
libraryItem.id = getId('li')
|
||||
libraryItem.ino = audiobook.ino
|
||||
libraryItem.libraryId = audiobook.libraryId
|
||||
libraryItem.folderId = audiobook.folderId
|
||||
@ -154,7 +160,7 @@ function makeLibraryItemFromOldAb(audiobook) {
|
||||
libraryItem.ctimeMs = audiobook.ctimeMs || 0
|
||||
libraryItem.birthtimeMs = audiobook.birthtimeMs || 0
|
||||
libraryItem.addedAt = audiobook.addedAt
|
||||
libraryItem.lastUpdate = audiobook.lastUpdate
|
||||
libraryItem.updatedAt = audiobook.lastUpdate
|
||||
libraryItem.lastScan = audiobook.lastScan
|
||||
libraryItem.scanVersion = audiobook.scanVersion
|
||||
libraryItem.isMissing = audiobook.isMissing
|
||||
@ -179,12 +185,34 @@ function makeLibraryItemFromOldAb(audiobook) {
|
||||
bookEntity.tags = [...audiobook.tags]
|
||||
|
||||
var payload = makeFilesFromOldAb(audiobook)
|
||||
bookEntity.audioFiles = payload.audioFiles
|
||||
bookEntity.ebookFiles = payload.ebookFiles
|
||||
|
||||
if (payload.audioFiles.length) {
|
||||
var newAudiobook = new Audiobook()
|
||||
newAudiobook.id = audiobook.id
|
||||
newAudiobook.index = 1
|
||||
newAudiobook.name = 'default'
|
||||
newAudiobook.audioFiles = payload.audioFiles
|
||||
if (audiobook.chapters && audiobook.chapters.length) {
|
||||
bookEntity.chapters = audiobook.chapters.map(c => ({ ...c }))
|
||||
newAudiobook.chapters = audiobook.chapters.map(c => ({ ...c }))
|
||||
}
|
||||
newAudiobook.missingParts = audiobook.missingParts || []
|
||||
newAudiobook.addedAt = audiobook.addedAt
|
||||
newAudiobook.updatedAt = audiobook.lastUpdate
|
||||
|
||||
bookEntity.audiobooks.push(newAudiobook)
|
||||
}
|
||||
|
||||
var ebookIndex = 1
|
||||
payload.ebookFiles.forEach(ebookFile => {
|
||||
var newEBook = new EBook()
|
||||
newEBook.id = getId('eb')
|
||||
newEBook.index = ebookIndex++
|
||||
newEBook.name = ebookFile.metadata.filenameNoExt
|
||||
newEBook.ebookFile = ebookFile
|
||||
newEBook.addedAt = audiobook.addedAt
|
||||
newEBook.updatedAt = audiobook.lastUpdate
|
||||
|
||||
bookEntity.ebooks.push(newEBook)
|
||||
})
|
||||
|
||||
libraryItem.media = bookEntity
|
||||
libraryItem.libraryFiles = payload.libraryFiles
|
||||
@ -234,8 +262,8 @@ async function migrateLibraryItems(db) {
|
||||
}
|
||||
module.exports.migrateLibraryItems = migrateLibraryItems
|
||||
|
||||
function cleanUserObject(db, userObj) {
|
||||
|
||||
function cleanUserObject(db, userObj) {
|
||||
var cleanedUserPayload = {
|
||||
...userObj,
|
||||
libraryItemProgress: [],
|
||||
|
Loading…
Reference in New Issue
Block a user