mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-02-04 01:09:40 +00:00
New api routes, updating web client pages, audiobooks to libraryItem migration
This commit is contained in:
parent
b97ed953f7
commit
2a30cc428f
51 changed files with 1225 additions and 654 deletions
|
|
@ -1,129 +0,0 @@
|
|||
class AudioFileMetadata {
|
||||
constructor(metadata) {
|
||||
this.tagAlbum = null
|
||||
this.tagArtist = null
|
||||
this.tagGenre = null
|
||||
this.tagTitle = null
|
||||
this.tagSeries = null
|
||||
this.tagSeriesPart = null
|
||||
this.tagTrack = null
|
||||
this.tagDisc = null
|
||||
this.tagSubtitle = null
|
||||
this.tagAlbumArtist = null
|
||||
this.tagDate = null
|
||||
this.tagComposer = null
|
||||
this.tagPublisher = null
|
||||
this.tagComment = null
|
||||
this.tagDescription = null
|
||||
this.tagEncoder = null
|
||||
this.tagEncodedBy = null
|
||||
this.tagIsbn = null
|
||||
this.tagLanguage = null
|
||||
this.tagASIN = null
|
||||
|
||||
if (metadata) {
|
||||
this.construct(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
// Only return the tags that are actually set
|
||||
var json = {}
|
||||
for (const key in this) {
|
||||
if (key.startsWith('tag') && this[key]) {
|
||||
json[key] = this[key]
|
||||
}
|
||||
}
|
||||
return json
|
||||
}
|
||||
|
||||
construct(metadata) {
|
||||
this.tagAlbum = metadata.tagAlbum || null
|
||||
this.tagArtist = metadata.tagArtist || null
|
||||
this.tagGenre = metadata.tagGenre || null
|
||||
this.tagTitle = metadata.tagTitle || null
|
||||
this.tagSeries = metadata.tagSeries || null
|
||||
this.tagSeriesPart = metadata.tagSeriesPart || null
|
||||
this.tagTrack = metadata.tagTrack || null
|
||||
this.tagDisc = metadata.tagDisc || null
|
||||
this.tagSubtitle = metadata.tagSubtitle || null
|
||||
this.tagAlbumArtist = metadata.tagAlbumArtist || null
|
||||
this.tagDate = metadata.tagDate || null
|
||||
this.tagComposer = metadata.tagComposer || null
|
||||
this.tagPublisher = metadata.tagPublisher || null
|
||||
this.tagComment = metadata.tagComment || null
|
||||
this.tagDescription = metadata.tagDescription || null
|
||||
this.tagEncoder = metadata.tagEncoder || null
|
||||
this.tagEncodedBy = metadata.tagEncodedBy || null
|
||||
this.tagIsbn = metadata.tagIsbn || null
|
||||
this.tagLanguage = metadata.tagLanguage || null
|
||||
this.tagASIN = metadata.tagASIN || null
|
||||
}
|
||||
|
||||
// Data parsed in prober.js
|
||||
setData(payload) {
|
||||
this.tagAlbum = payload.file_tag_album || null
|
||||
this.tagArtist = payload.file_tag_artist || null
|
||||
this.tagGenre = payload.file_tag_genre || null
|
||||
this.tagTitle = payload.file_tag_title || null
|
||||
this.tagSeries = payload.file_tag_series || null
|
||||
this.tagSeriesPart = payload.file_tag_seriespart || null
|
||||
this.tagTrack = payload.file_tag_track || null
|
||||
this.tagDisc = payload.file_tag_disc || null
|
||||
this.tagSubtitle = payload.file_tag_subtitle || null
|
||||
this.tagAlbumArtist = payload.file_tag_albumartist || null
|
||||
this.tagDate = payload.file_tag_date || null
|
||||
this.tagComposer = payload.file_tag_composer || null
|
||||
this.tagPublisher = payload.file_tag_publisher || null
|
||||
this.tagComment = payload.file_tag_comment || null
|
||||
this.tagDescription = payload.file_tag_description || null
|
||||
this.tagEncoder = payload.file_tag_encoder || null
|
||||
this.tagEncodedBy = payload.file_tag_encodedby || null
|
||||
this.tagIsbn = payload.file_tag_isbn || null
|
||||
this.tagLanguage = payload.file_tag_language || null
|
||||
this.tagASIN = payload.file_tag_asin || null
|
||||
}
|
||||
|
||||
updateData(payload) {
|
||||
const dataMap = {
|
||||
tagAlbum: payload.file_tag_album || null,
|
||||
tagArtist: payload.file_tag_artist || null,
|
||||
tagGenre: payload.file_tag_genre || null,
|
||||
tagTitle: payload.file_tag_title || null,
|
||||
tagSeries: payload.file_tag_series || null,
|
||||
tagSeriesPart: payload.file_tag_seriespart || null,
|
||||
tagTrack: payload.file_tag_track || null,
|
||||
tagDisc: payload.file_tag_disc || null,
|
||||
tagSubtitle: payload.file_tag_subtitle || null,
|
||||
tagAlbumArtist: payload.file_tag_albumartist || null,
|
||||
tagDate: payload.file_tag_date || null,
|
||||
tagComposer: payload.file_tag_composer || null,
|
||||
tagPublisher: payload.file_tag_publisher || null,
|
||||
tagComment: payload.file_tag_comment || null,
|
||||
tagDescription: payload.file_tag_description || null,
|
||||
tagEncoder: payload.file_tag_encoder || null,
|
||||
tagEncodedBy: payload.file_tag_encodedby || null,
|
||||
tagIsbn: payload.file_tag_isbn || null,
|
||||
tagLanguage: payload.file_tag_language || null,
|
||||
tagASIN: payload.file_tag_asin || null
|
||||
}
|
||||
|
||||
var hasUpdates = false
|
||||
for (const key in dataMap) {
|
||||
if (dataMap[key] !== this[key]) {
|
||||
this[key] = dataMap[key]
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
isEqual(audioFileMetadata) {
|
||||
if (!audioFileMetadata || !audioFileMetadata.toJSON) return false
|
||||
for (const key in audioFileMetadata.toJSON()) {
|
||||
if (audioFileMetadata[key] !== this[key]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
module.exports = AudioFileMetadata
|
||||
|
|
@ -17,15 +17,15 @@ class LibraryItem {
|
|||
this.ctimeMs = null
|
||||
this.birthtimeMs = null
|
||||
this.addedAt = null
|
||||
this.lastUpdate = null
|
||||
this.updatedAt = null
|
||||
this.lastScan = null
|
||||
this.scanVersion = null
|
||||
|
||||
// Entity was scanned and not found
|
||||
this.isMissing = false
|
||||
|
||||
this.entityType = null
|
||||
this.entity = null
|
||||
this.mediaType = null
|
||||
this.media = null
|
||||
|
||||
this.libraryFiles = []
|
||||
|
||||
|
|
@ -45,17 +45,17 @@ class LibraryItem {
|
|||
this.ctimeMs = libraryItem.ctimeMs || 0
|
||||
this.birthtimeMs = libraryItem.birthtimeMs || 0
|
||||
this.addedAt = libraryItem.addedAt
|
||||
this.lastUpdate = libraryItem.lastUpdate || this.addedAt
|
||||
this.updatedAt = libraryItem.updatedAt || this.addedAt
|
||||
this.lastScan = libraryItem.lastScan || null
|
||||
this.scanVersion = libraryItem.scanVersion || null
|
||||
|
||||
this.isMissing = !!libraryItem.isMissing
|
||||
|
||||
this.entityType = libraryItem.entityType
|
||||
if (this.entityType === 'book') {
|
||||
this.entity = new Book(libraryItem.entity)
|
||||
} else if (this.entityType === 'podcast') {
|
||||
this.entity = new Podcast(libraryItem.entity)
|
||||
this.mediaType = libraryItem.mediaType
|
||||
if (this.mediaType === 'book') {
|
||||
this.media = new Book(libraryItem.media)
|
||||
} else if (this.mediaType === 'podcast') {
|
||||
this.media = new Podcast(libraryItem.media)
|
||||
}
|
||||
|
||||
this.libraryFiles = libraryItem.libraryFiles.map(f => new LibraryFile(f))
|
||||
|
|
@ -73,14 +73,64 @@ class LibraryItem {
|
|||
ctimeMs: this.ctimeMs,
|
||||
birthtimeMs: this.birthtimeMs,
|
||||
addedAt: this.addedAt,
|
||||
lastUpdate: this.lastUpdate,
|
||||
updatedAt: this.updatedAt,
|
||||
lastScan: this.lastScan,
|
||||
scanVersion: this.scanVersion,
|
||||
isMissing: !!this.isMissing,
|
||||
entityType: this.entityType,
|
||||
entity: this.entity.toJSON(),
|
||||
mediaType: this.mediaType,
|
||||
media: this.media.toJSON(),
|
||||
libraryFiles: this.libraryFiles.map(f => f.toJSON())
|
||||
}
|
||||
}
|
||||
|
||||
toJSONMinified() {
|
||||
return {
|
||||
id: this.id,
|
||||
ino: this.ino,
|
||||
libraryId: this.libraryId,
|
||||
folderId: this.folderId,
|
||||
path: this.path,
|
||||
relPath: this.relPath,
|
||||
mtimeMs: this.mtimeMs,
|
||||
ctimeMs: this.ctimeMs,
|
||||
birthtimeMs: this.birthtimeMs,
|
||||
addedAt: this.addedAt,
|
||||
updatedAt: this.updatedAt,
|
||||
isMissing: !!this.isMissing,
|
||||
mediaType: this.mediaType,
|
||||
media: this.media.toJSONMinified(),
|
||||
numFiles: this.libraryFiles.length
|
||||
}
|
||||
}
|
||||
|
||||
// Adds additional helpful fields like media duration, tracks, etc.
|
||||
toJSONExpanded() {
|
||||
return {
|
||||
id: this.id,
|
||||
ino: this.ino,
|
||||
libraryId: this.libraryId,
|
||||
folderId: this.folderId,
|
||||
path: this.path,
|
||||
relPath: this.relPath,
|
||||
mtimeMs: this.mtimeMs,
|
||||
ctimeMs: this.ctimeMs,
|
||||
birthtimeMs: this.birthtimeMs,
|
||||
addedAt: this.addedAt,
|
||||
updatedAt: this.updatedAt,
|
||||
lastScan: this.lastScan,
|
||||
scanVersion: this.scanVersion,
|
||||
isMissing: !!this.isMissing,
|
||||
mediaType: this.mediaType,
|
||||
media: this.media.toJSONExpanded(),
|
||||
libraryFiles: this.libraryFiles.map(f => f.toJSON()),
|
||||
size: this.size
|
||||
}
|
||||
}
|
||||
|
||||
get size() {
|
||||
var total = 0
|
||||
this.libraryFiles.forEach((lf) => total += lf.metadata.size)
|
||||
return total
|
||||
}
|
||||
}
|
||||
module.exports = LibraryItem
|
||||
|
|
@ -7,7 +7,6 @@ class Book {
|
|||
this.metadata = null
|
||||
|
||||
this.coverPath = null
|
||||
this.relCoverPath = null
|
||||
this.tags = []
|
||||
this.audioFiles = []
|
||||
this.ebookFiles = []
|
||||
|
|
@ -21,7 +20,6 @@ class Book {
|
|||
construct(book) {
|
||||
this.metadata = new BookMetadata(book.metadata)
|
||||
this.coverPath = book.coverPath
|
||||
this.relCoverPath = book.relCoverPath
|
||||
this.tags = [...book.tags]
|
||||
this.audioFiles = book.audioFiles.map(f => new AudioFile(f))
|
||||
this.ebookFiles = book.ebookFiles.map(f => new EBookFile(f))
|
||||
|
|
@ -32,12 +30,53 @@ class Book {
|
|||
return {
|
||||
metadata: this.metadata.toJSON(),
|
||||
coverPath: this.coverPath,
|
||||
relCoverPath: this.relCoverPath,
|
||||
tags: [...this.tags],
|
||||
audioFiles: this.audioFiles.map(f => f.toJSON()),
|
||||
ebookFiles: this.ebookFiles.map(f => f.toJSON()),
|
||||
chapters: this.chapters.map(c => ({ ...c }))
|
||||
}
|
||||
}
|
||||
|
||||
toJSONMinified() {
|
||||
return {
|
||||
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,
|
||||
duration: this.duration,
|
||||
size: this.size
|
||||
}
|
||||
}
|
||||
|
||||
toJSONExpanded() {
|
||||
return {
|
||||
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 })),
|
||||
duration: this.duration,
|
||||
size: this.size,
|
||||
tracks: this.tracks.map(t => t.toJSON())
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
module.exports = Book
|
||||
|
|
@ -6,8 +6,8 @@ class Podcast {
|
|||
this.id = null
|
||||
|
||||
this.metadata = null
|
||||
this.cover = null
|
||||
this.coverFullPath = null
|
||||
this.coverPath = null
|
||||
this.tags = []
|
||||
this.episodes = []
|
||||
|
||||
this.createdAt = null
|
||||
|
|
@ -21,8 +21,8 @@ class Podcast {
|
|||
construct(podcast) {
|
||||
this.id = podcast.id
|
||||
this.metadata = new PodcastMetadata(podcast.metadata)
|
||||
this.cover = podcast.cover
|
||||
this.coverFullPath = podcast.coverFullPath
|
||||
this.coverPath = podcast.coverPath
|
||||
this.tags = [...podcast.tags]
|
||||
this.episodes = podcast.episodes.map((e) => new PodcastEpisode(e))
|
||||
this.createdAt = podcast.createdAt
|
||||
this.lastUpdate = podcast.lastUpdate
|
||||
|
|
@ -32,8 +32,32 @@ class Podcast {
|
|||
return {
|
||||
id: this.id,
|
||||
metadata: this.metadata.toJSON(),
|
||||
cover: this.cover,
|
||||
coverFullPath: this.coverFullPath,
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
episodes: this.episodes.map(e => e.toJSON()),
|
||||
createdAt: this.createdAt,
|
||||
lastUpdate: this.lastUpdate
|
||||
}
|
||||
}
|
||||
|
||||
toJSONMinified() {
|
||||
return {
|
||||
id: this.id,
|
||||
metadata: this.metadata.toJSON(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
episodes: this.episodes.map(e => e.toJSON()),
|
||||
createdAt: this.createdAt,
|
||||
lastUpdate: this.lastUpdate
|
||||
}
|
||||
}
|
||||
|
||||
toJSONExpanded() {
|
||||
return {
|
||||
id: this.id,
|
||||
metadata: this.metadata.toJSONExpanded(),
|
||||
coverPath: this.coverPath,
|
||||
tags: [...this.tags],
|
||||
episodes: this.episodes.map(e => e.toJSON()),
|
||||
createdAt: this.createdAt,
|
||||
lastUpdate: this.lastUpdate
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
const globals = require('../../utils/globals')
|
||||
const FileMetadata = require('../metadata/FileMetadata')
|
||||
|
||||
class LibraryFile {
|
||||
|
|
@ -24,8 +25,18 @@ class LibraryFile {
|
|||
ino: this.ino,
|
||||
metadata: this.metadata.toJSON(),
|
||||
addedAt: this.addedAt,
|
||||
updatedAt: this.updatedAt
|
||||
updatedAt: this.updatedAt,
|
||||
fileType: this.fileType
|
||||
}
|
||||
}
|
||||
|
||||
get fileType() {
|
||||
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.TextFileTypes.includes(this.metadata.format)) return 'text'
|
||||
if (globals.MetadataFileTypes.includes(this.metadata.format)) return 'metadata'
|
||||
return 'unknown'
|
||||
}
|
||||
}
|
||||
module.exports = LibraryFile
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
const { isNullOrNaN } = require('../utils/index')
|
||||
const AudioFileMetadata = require('./metadata/AudioMetaTags')
|
||||
const { isNullOrNaN } = require('../../utils/index')
|
||||
const AudioFileMetadata = require('../metadata/AudioMetaTags')
|
||||
|
||||
class AudioFile {
|
||||
constructor(data) {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
var { bytesPretty } = require('../utils/fileUtils')
|
||||
var { bytesPretty } = require('../../utils/fileUtils')
|
||||
|
||||
class AudioTrack {
|
||||
constructor(audioTrack = null) {
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
const Path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const { bytesPretty, readTextFile, getIno } = require('../utils/fileUtils')
|
||||
const { comparePaths, getId, elapsedPretty } = require('../utils/index')
|
||||
const { parseOpfMetadataXML } = require('../utils/parseOpfMetadata')
|
||||
const { extractCoverArt } = require('../utils/ffmpegHelpers')
|
||||
const nfoGenerator = require('../utils/nfoGenerator')
|
||||
const abmetadataGenerator = require('../utils/abmetadataGenerator')
|
||||
const Logger = require('../Logger')
|
||||
const { bytesPretty, readTextFile, getIno } = require('../../utils/fileUtils')
|
||||
const { comparePaths, getId, elapsedPretty } = require('../../utils/index')
|
||||
const { parseOpfMetadataXML } = require('../../utils/parseOpfMetadata')
|
||||
const { extractCoverArt } = require('../../utils/ffmpegHelpers')
|
||||
const nfoGenerator = require('../../utils/nfoGenerator')
|
||||
const abmetadataGenerator = require('../../utils/abmetadataGenerator')
|
||||
const Logger = require('../../Logger')
|
||||
const Book = require('./Book')
|
||||
const AudioTrack = require('./AudioTrack')
|
||||
const AudioFile = require('./AudioFile')
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
const { getId } = require('../utils/index')
|
||||
const Logger = require('../Logger')
|
||||
const { getId } = require('../../utils/index')
|
||||
const Logger = require('../../Logger')
|
||||
|
||||
class Author {
|
||||
constructor(author = null) {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
const Path = require('path')
|
||||
const Logger = require('../Logger')
|
||||
const parseAuthors = require('../utils/parseAuthors')
|
||||
const Logger = require('../../Logger')
|
||||
const parseAuthors = require('../../utils/parseAuthors')
|
||||
|
||||
class Book {
|
||||
constructor(book = null) {
|
||||
|
|
@ -13,6 +13,7 @@ class BookMetadata {
|
|||
this.isbn = null
|
||||
this.asin = null
|
||||
this.language = null
|
||||
this.explicit = false
|
||||
|
||||
if (metadata) {
|
||||
this.construct(metadata)
|
||||
|
|
@ -33,6 +34,7 @@ class BookMetadata {
|
|||
this.isbn = metadata.isbn
|
||||
this.asin = metadata.asin
|
||||
this.language = metadata.language
|
||||
this.explicit = metadata.explicit
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
|
|
@ -49,8 +51,54 @@ class BookMetadata {
|
|||
description: this.description,
|
||||
isbn: this.isbn,
|
||||
asin: this.asin,
|
||||
language: this.language
|
||||
language: this.language,
|
||||
explicit: this.explicit
|
||||
}
|
||||
}
|
||||
|
||||
toJSONExpanded() {
|
||||
return {
|
||||
title: this.title,
|
||||
subtitle: this.subtitle,
|
||||
authors: this.authors.map(a => ({ ...a })), // Author JSONMinimal with name and id
|
||||
narrators: [...this.narrators],
|
||||
series: this.series.map(s => ({ ...s })),
|
||||
genres: [...this.genres],
|
||||
publishedYear: this.publishedYear,
|
||||
publishedDate: this.publishedDate,
|
||||
publisher: this.publisher,
|
||||
description: this.description,
|
||||
isbn: this.isbn,
|
||||
asin: this.asin,
|
||||
language: this.language,
|
||||
explicit: this.explicit,
|
||||
authorName: this.authorName,
|
||||
narratorName: this.narratorName
|
||||
}
|
||||
}
|
||||
|
||||
get titleIgnorePrefix() {
|
||||
if (!this.title) return ''
|
||||
if (this.title.toLowerCase().startsWith('the ')) {
|
||||
return this.title.substr(4) + ', The'
|
||||
}
|
||||
return this.title
|
||||
}
|
||||
get authorName() {
|
||||
return this.authors.map(au => au.name).join(', ')
|
||||
}
|
||||
get narratorName() {
|
||||
return this.narrators.join(', ')
|
||||
}
|
||||
|
||||
hasAuthor(authorName) {
|
||||
return !!this.authors.find(au => au.name == authorName)
|
||||
}
|
||||
hasSeries(seriesName) {
|
||||
return !!this.series.find(se => se.name == seriesName)
|
||||
}
|
||||
hasNarrator(narratorName) {
|
||||
return this.narrators.includes(narratorName)
|
||||
}
|
||||
}
|
||||
module.exports = BookMetadata
|
||||
|
|
@ -41,5 +41,10 @@ class FileMetadata {
|
|||
clone() {
|
||||
return new FileMetadata(this.toJSON())
|
||||
}
|
||||
|
||||
get format() {
|
||||
if (!this.ext) return ''
|
||||
return this.ext.slice(1)
|
||||
}
|
||||
}
|
||||
module.exports = FileMetadata
|
||||
|
|
@ -9,6 +9,7 @@ class PodcastMetadata {
|
|||
this.itunesPageUrl = null
|
||||
this.itunesId = null
|
||||
this.itunesArtistId = null
|
||||
this.explicit = false
|
||||
|
||||
if (metadata) {
|
||||
this.construct(metadata)
|
||||
|
|
@ -25,6 +26,7 @@ class PodcastMetadata {
|
|||
this.itunesPageUrl = metadata.itunesPageUrl
|
||||
this.itunesId = metadata.itunesId
|
||||
this.itunesArtistId = metadata.itunesArtistId
|
||||
this.explicit = metadata.explicit
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
|
|
@ -38,7 +40,12 @@ class PodcastMetadata {
|
|||
itunesPageUrl: this.itunesPageUrl,
|
||||
itunesId: this.itunesId,
|
||||
itunesArtistId: this.itunesArtistId,
|
||||
explicit: this.explicit
|
||||
}
|
||||
}
|
||||
|
||||
toJSONExpanded() {
|
||||
return this.toJSON()
|
||||
}
|
||||
}
|
||||
module.exports = PodcastMetadata
|
||||
Loading…
Add table
Add a link
Reference in a new issue