From 65793f7109eb44ecc9684077e69c1b846703105a Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 8 Mar 2022 19:31:44 -0600 Subject: [PATCH] Start of new data model --- client/pages/audiobook/_id/edit.vue | 3 +- docs/LibraryItemModelDemo.js | 166 ++++++++++++++++++ server/objects/Audiobook.js | 2 +- server/objects/LibraryItem.js | 86 +++++++++ server/objects/entities/Author.js | 45 +++++ server/objects/entities/Book.js | 41 +++++ server/objects/entities/Podcast.js | 43 +++++ server/objects/entities/PodcastEpisode.js | 38 ++++ server/objects/entities/Series.js | 40 +++++ server/objects/{ => files}/AudioFile.js | 6 +- server/objects/files/EBookFile.js | 34 ++++ server/objects/files/LibraryFile.js | 31 ++++ .../{ => metadata}/AudioFileMetadata.js | 0 server/objects/metadata/BookMetadata.js | 56 ++++++ server/objects/metadata/FileMetadata.js | 41 +++++ server/objects/metadata/PodcastMetadata.js | 44 +++++ server/scanner/AudioFileScanner.js | 2 +- server/scanner/AudioProbeData.js | 2 +- 18 files changed, 672 insertions(+), 8 deletions(-) create mode 100644 docs/LibraryItemModelDemo.js create mode 100644 server/objects/LibraryItem.js create mode 100644 server/objects/entities/Author.js create mode 100644 server/objects/entities/Book.js create mode 100644 server/objects/entities/Podcast.js create mode 100644 server/objects/entities/PodcastEpisode.js create mode 100644 server/objects/entities/Series.js rename server/objects/{ => files}/AudioFile.js (97%) create mode 100644 server/objects/files/EBookFile.js create mode 100644 server/objects/files/LibraryFile.js rename server/objects/{ => metadata}/AudioFileMetadata.js (100%) create mode 100644 server/objects/metadata/BookMetadata.js create mode 100644 server/objects/metadata/FileMetadata.js create mode 100644 server/objects/metadata/PodcastMetadata.js diff --git a/client/pages/audiobook/_id/edit.vue b/client/pages/audiobook/_id/edit.vue index 81fd888e..4542f171 100644 --- a/client/pages/audiobook/_id/edit.vue +++ b/client/pages/audiobook/_id/edit.vue @@ -275,7 +275,6 @@ export default { return 'check_circle' } } - }, - mounted() {} + } } diff --git a/docs/LibraryItemModelDemo.js b/docs/LibraryItemModelDemo.js new file mode 100644 index 00000000..da7428ae --- /dev/null +++ b/docs/LibraryItemModelDemo.js @@ -0,0 +1,166 @@ +/* + 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, + entityType: 'book', + entity: { // Book.js + 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, + 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 : (Metatags/ID3 tags - only stores values that are found) + tagAlbum: '', + tagArtist: '', + tagGenre: '', + tagTitle: '', + tagSeries: '', + tagSeriesPart: '', + tagTrack: '', + tagDisc: '', + tagSubtitle: '', + tagAlbumArtist: '', + tagDate: '', + tagComposer: '', + tagPublisher: '', + tagComment: '', + tagDescription: '', + tagEncoder: '', + tagEncodedBy: '', + tagIsbn: '', + tagLanguage: '', + tagASIN: '' + }, + addedAt: 1646784672127, + lastUpdate: 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, + lastUpdate: 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, + lastUpdate: 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, + lastUpdate: 1646784672127 + } + ] +}) \ No newline at end of file diff --git a/server/objects/Audiobook.js b/server/objects/Audiobook.js index 1cbe8cda..799eaa2a 100644 --- a/server/objects/Audiobook.js +++ b/server/objects/Audiobook.js @@ -9,7 +9,7 @@ const abmetadataGenerator = require('../utils/abmetadataGenerator') const Logger = require('../Logger') const Book = require('./Book') const AudioTrack = require('./AudioTrack') -const AudioFile = require('./AudioFile') +const AudioFile = require('./files/AudioFile') const AudiobookFile = require('./AudiobookFile') class Audiobook { diff --git a/server/objects/LibraryItem.js b/server/objects/LibraryItem.js new file mode 100644 index 00000000..971b3545 --- /dev/null +++ b/server/objects/LibraryItem.js @@ -0,0 +1,86 @@ +const Logger = require('../Logger') +const LibraryFile = require('./files/LibraryFile') +const Book = require('./entities/Book') +const Podcast = require('./entities/Podcast') + +class LibraryItem { + constructor(libraryItem = null) { + this.id = null + this.ino = null // Inode + + this.libraryId = null + this.folderId = null + + this.path = null + this.relPath = null + this.mtimeMs = null + this.ctimeMs = null + this.birthtimeMs = null + this.addedAt = null + this.lastUpdate = null + this.lastScan = null + this.scanVersion = null + + // Entity was scanned and not found + this.isMissing = false + + this.entityType = null + this.entity = null + + this.libraryFiles = [] + + if (libraryItem) { + this.construct(libraryItem) + } + } + + construct(libraryItem) { + this.id = libraryItem.id + this.ino = libraryItem.ino || null + this.libraryId = libraryItem.libraryId + this.folderId = libraryItem.folderId + this.path = libraryItem.path + this.relPath = libraryItem.relPath + this.mtimeMs = libraryItem.mtimeMs || 0 + this.ctimeMs = libraryItem.ctimeMs || 0 + this.birthtimeMs = libraryItem.birthtimeMs || 0 + this.addedAt = libraryItem.addedAt + this.lastUpdate = libraryItem.lastUpdate || 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.libraryFiles = libraryItem.libraryFiles.map(f => new LibraryFile(f)) + } + + toJSON() { + 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, + lastUpdate: this.lastUpdate, + lastScan: this.lastScan, + scanVersion: this.scanVersion, + isMissing: !!this.isMissing, + entityType: this.entityType, + entity: this.entity.toJSON(), + libraryFiles: this.libraryFiles.map(f => f.toJSON()) + } + } +} +module.exports = LibraryItem \ No newline at end of file diff --git a/server/objects/entities/Author.js b/server/objects/entities/Author.js new file mode 100644 index 00000000..95ca9403 --- /dev/null +++ b/server/objects/entities/Author.js @@ -0,0 +1,45 @@ +class Author { + constructor(author) { + this.id = null + this.asin = null + this.name = null + this.imagePath = null + this.imageFullPath = null + this.addedAt = null + this.updatedAt = null + + if (author) { + this.construct(author) + } + } + + construct(author) { + this.id = author.id + this.asin = author.asin + this.name = author.name + this.imagePath = author.imagePath + this.imageFullPath = author.imageFullPath + this.addedAt = author.addedAt + this.updatedAt = author.updatedAt + } + + toJSON() { + return { + id: this.id, + asin: this.asin, + name: this.name, + imagePath: this.imagePath, + imageFullPath: this.imageFullPath, + addedAt: this.addedAt, + updatedAt: this.updatedAt + } + } + + toJSONMinimal() { + return { + id: this.id, + name: this.name + } + } +} +module.exports = Author \ No newline at end of file diff --git a/server/objects/entities/Book.js b/server/objects/entities/Book.js new file mode 100644 index 00000000..4779b651 --- /dev/null +++ b/server/objects/entities/Book.js @@ -0,0 +1,41 @@ +const BookMetadata = require('../metadata/BookMetadata') +const AudioFile = require('../files/AudioFile') +const EBookFile = require('../files/EBookFile') +const AudioTrack = require('../AudioTrack') + +class Book { + constructor(book) { + this.metadata = null + + this.tags = [] + this.audioFiles = [] + this.ebookFiles = [] + this.audioTracks = [] + this.chapters = [] + + if (books) { + this.construct(book) + } + } + + construct(book) { + this.metadata = new BookMetadata(book.metadata) + this.tags = [...book.tags] + this.audioFiles = book.audioFiles.map(f => new AudioFile(f)) + this.ebookFiles = book.ebookFiles.map(f => new EBookFile(f)) + this.audioTracks = book.audioTracks.map(a => new AudioTrack(a)) + this.chapters = book.chapters.map(c => ({ ...c })) + } + + toJSON() { + return { + metadata: this.metadata.toJSON(), + tags: [...this.tags], + audioFiles: this.audioFiles.map(f => f.toJSON()), + ebookFiles: this.ebookFiles.map(f => f.toJSON()), + audioTracks: this.audioTracks.map(a => a.toJSON()), + chapters: this.chapters.map(c => ({ ...c })) + } + } +} +module.exports = Book \ No newline at end of file diff --git a/server/objects/entities/Podcast.js b/server/objects/entities/Podcast.js new file mode 100644 index 00000000..a8913cf8 --- /dev/null +++ b/server/objects/entities/Podcast.js @@ -0,0 +1,43 @@ +const PodcastEpisode = require('./PodcastEpisode') +const PodcastMetadata = require('../metadata/PodcastMetadata') + +class Podcast { + constructor(podcast) { + this.id = null + + this.metadata = null + this.cover = null + this.coverFullPath = null + this.episodes = [] + + this.createdAt = null + this.lastUpdate = null + + if (podcast) { + this.construct(podcast) + } + } + + construct(podcast) { + this.id = podcast.id + this.metadata = new PodcastMetadata(podcast.metadata) + this.cover = podcast.cover + this.coverFullPath = podcast.coverFullPath + this.episodes = podcast.episodes.map((e) => new PodcastEpisode(e)) + this.createdAt = podcast.createdAt + this.lastUpdate = podcast.lastUpdate + } + + toJSON() { + return { + id: this.id, + metadata: this.metadata.toJSON(), + cover: this.cover, + coverFullPath: this.coverFullPath, + episodes: this.episodes.map(e => e.toJSON()), + createdAt: this.createdAt, + lastUpdate: this.lastUpdate + } + } +} +module.exports = Podcast \ No newline at end of file diff --git a/server/objects/entities/PodcastEpisode.js b/server/objects/entities/PodcastEpisode.js new file mode 100644 index 00000000..08070e1c --- /dev/null +++ b/server/objects/entities/PodcastEpisode.js @@ -0,0 +1,38 @@ +const AudioFile = require('../files/AudioFile') + +class PodcastEpisode { + constructor(episode) { + this.id = null + this.podcastId = null + this.episodeNumber = null + + this.audioFile = null + this.addedAt = null + this.updatedAt = null + + if (episode) { + this.construct(episode) + } + } + + construct(episode) { + this.id = episode.id + this.podcastId = episode.podcastId + this.episodeNumber = episode.episodeNumber + this.audioFile = new AudioFile(episode.audioFile) + this.addedAt = episode.addedAt + this.updatedAt = episode.updatedAt + } + + toJSON() { + return { + id: this.id, + podcastId: this.podcastId, + episodeNumber: this.episodeNumber, + audioFile: this.audioFile.toJSON(), + addedAt: this.addedAt, + updatedAt: this.updatedAt + } + } +} +module.exports = PodcastEpisode \ No newline at end of file diff --git a/server/objects/entities/Series.js b/server/objects/entities/Series.js new file mode 100644 index 00000000..b6941c1e --- /dev/null +++ b/server/objects/entities/Series.js @@ -0,0 +1,40 @@ +class Series { + constructor(series) { + this.id = null + this.name = null + this.sequence = null + this.addedAt = null + this.updatedAt = null + + if (series) { + this.construct(series) + } + } + + construct(series) { + this.id = series.id + this.name = series.name + this.sequence = series.sequence + this.addedAt = series.addedAt + this.updatedAt = series.updatedAt + } + + toJSON() { + return { + id: this.id, + name: this.name, + sequence: this.sequence, + addedAt: this.addedAt, + updatedAt: this.updatedAt + } + } + + toJSONMinimal() { + return { + id: this.id, + name: this.name, + sequence: this.sequence + } + } +} +module.exports = Series \ No newline at end of file diff --git a/server/objects/AudioFile.js b/server/objects/files/AudioFile.js similarity index 97% rename from server/objects/AudioFile.js rename to server/objects/files/AudioFile.js index 65a228be..aa3425ee 100644 --- a/server/objects/AudioFile.js +++ b/server/objects/files/AudioFile.js @@ -1,7 +1,7 @@ -const { isNullOrNaN } = require('../utils/index') +const { isNullOrNaN } = require('../../utils/index') -const Logger = require('../Logger') -const AudioFileMetadata = require('./AudioFileMetadata') +const Logger = require('../../Logger') +const AudioFileMetadata = require('../metadata/AudioFileMetadata') class AudioFile { constructor(data) { diff --git a/server/objects/files/EBookFile.js b/server/objects/files/EBookFile.js new file mode 100644 index 00000000..a964e72b --- /dev/null +++ b/server/objects/files/EBookFile.js @@ -0,0 +1,34 @@ +const FileMetadata = require('../metadata/FileMetadata') + +class EBookFile { + constructor(file) { + this.ino = null + this.metadata = null + this.ebookFormat = null + this.addedAt = null + this.lastUpdate = null + + if (file) { + this.construct(file) + } + } + + construct(file) { + this.ino = file.ino + this.metadata = new FileMetadata(file) + this.ebookFormat = file.ebookFormat + this.addedAt = file.addedAt + this.lastUpdate = file.lastUpdate + } + + toJSON() { + return { + ino: this.ino, + metadata: this.metadata.toJSON(), + ebookFormat: this.ebookFormat, + addedAt: this.addedAt, + lastUpdate: this.lastUpdate + } + } +} +module.exports = EBookFile \ No newline at end of file diff --git a/server/objects/files/LibraryFile.js b/server/objects/files/LibraryFile.js new file mode 100644 index 00000000..5d444a78 --- /dev/null +++ b/server/objects/files/LibraryFile.js @@ -0,0 +1,31 @@ +const FileMetadata = require('../metadata/FileMetadata') + +class LibraryFile { + constructor(file) { + this.ino = null + this.metadata = null + this.addedAt = null + this.updatedAt = null + + if (file) { + this.construct(file) + } + } + + construct(file) { + this.ino = file.ino + this.metadata = new FileMetadata(file.metadata) + this.addedAt = file.addedAt + this.updatedAt = file.updatedAt + } + + toJSON() { + return { + ino: this.ino, + metadata: this.metadata.toJSON(), + addedAt: this.addedAt, + updatedAt: this.updatedAt + } + } +} +module.exports = LibraryFile \ No newline at end of file diff --git a/server/objects/AudioFileMetadata.js b/server/objects/metadata/AudioFileMetadata.js similarity index 100% rename from server/objects/AudioFileMetadata.js rename to server/objects/metadata/AudioFileMetadata.js diff --git a/server/objects/metadata/BookMetadata.js b/server/objects/metadata/BookMetadata.js new file mode 100644 index 00000000..1d32c82a --- /dev/null +++ b/server/objects/metadata/BookMetadata.js @@ -0,0 +1,56 @@ +class BookMetadata { + constructor(metadata) { + this.title = null + this.subtitle = null + this.authors = [] + this.narrators = [] // Array of strings + this.series = [] + this.genres = [] // Array of strings + this.publishedYear = null + this.publishedDate = null + this.publisher = null + this.description = null + this.isbn = null + this.asin = null + this.language = null + + if (metadata) { + this.construct(metadata) + } + } + + construct(metadata) { + this.title = metadata.title + this.subtitle = metadata.subtitle + this.authors = metadata.authors.map(a => ({ ...a })) + this.narrators = [...metadata.narrators] + this.series = metadata.series.map(s => ({ ...s })) + this.genres = [...metadata.genres] + this.publishedYear = metadata.publishedYear + this.publishedDate = metadata.publishedDate + this.publisher = metadata.publisher + this.description = metadata.description + this.isbn = metadata.isbn + this.asin = metadata.asin + this.language = metadata.language + } + + toJSON() { + 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 + } + } +} +module.exports = BookMetadata \ No newline at end of file diff --git a/server/objects/metadata/FileMetadata.js b/server/objects/metadata/FileMetadata.js new file mode 100644 index 00000000..f7bb8eb2 --- /dev/null +++ b/server/objects/metadata/FileMetadata.js @@ -0,0 +1,41 @@ +class FileMetadata { + constructor(metadata) { + this.filename = null + this.ext = null + this.path = null + this.relPath = null + this.size = null + this.mtimeMs = null + this.ctimeMs = null + this.birthtimeMs = null + + if (metadata) { + this.construct(metadata) + } + } + + construct(metadata) { + this.filename = metadata.filename + this.ext = metadata.ext + this.path = metadata.path + this.relPath = metadata.relPath + this.size = metadata.size + this.mtimeMs = metadata.mtimeMs + this.ctimeMs = metadata.ctimeMs + this.birthtimeMs = metadata.birthtimeMs + } + + toJSON() { + return { + filename: this.filename, + ext: this.ext, + path: this.path, + relPath: this.relPath, + size: this.size, + mtimeMs: this.mtimeMs, + ctimeMs: this.ctimeMs, + birthtimeMs: this.birthtimeMs + } + } +} +module.exports = FileMetadata \ No newline at end of file diff --git a/server/objects/metadata/PodcastMetadata.js b/server/objects/metadata/PodcastMetadata.js new file mode 100644 index 00000000..54580729 --- /dev/null +++ b/server/objects/metadata/PodcastMetadata.js @@ -0,0 +1,44 @@ +class PodcastMetadata { + constructor(metadata) { + this.title = null + this.artist = null + this.description = null + this.releaseDate = null + this.genres = [] + this.feedUrl = null + this.itunesPageUrl = null + this.itunesId = null + this.itunesArtistId = null + + if (metadata) { + this.construct(metadata) + } + } + + construct(metadata) { + this.title = metadata.title + this.artist = metadata.artist + this.description = metadata.description + this.releaseDate = metadata.releaseDate + this.genres = [...metadata.genres] + this.feedUrl = metadata.feedUrl + this.itunesPageUrl = metadata.itunesPageUrl + this.itunesId = metadata.itunesId + this.itunesArtistId = metadata.itunesArtistId + } + + toJSON() { + return { + title: this.title, + artist: this.artist, + description: this.description, + releaseDate: this.releaseDate, + genres: [...this.genres], + feedUrl: this.feedUrl, + itunesPageUrl: this.itunesPageUrl, + itunesId: this.itunesId, + itunesArtistId: this.itunesArtistId, + } + } +} +module.exports = PodcastMetadata \ No newline at end of file diff --git a/server/scanner/AudioFileScanner.js b/server/scanner/AudioFileScanner.js index 3cf00833..d876a917 100644 --- a/server/scanner/AudioFileScanner.js +++ b/server/scanner/AudioFileScanner.js @@ -1,6 +1,6 @@ const Path = require('path') -const AudioFile = require('../objects/AudioFile') +const AudioFile = require('../objects/files/AudioFile') const prober = require('../utils/prober') const Logger = require('../Logger') diff --git a/server/scanner/AudioProbeData.js b/server/scanner/AudioProbeData.js index 05e09891..90c45e27 100644 --- a/server/scanner/AudioProbeData.js +++ b/server/scanner/AudioProbeData.js @@ -1,4 +1,4 @@ -const AudioFileMetadata = require('../objects/AudioFileMetadata') +const AudioFileMetadata = require('../objects/metadata/AudioFileMetadata') class AudioProbeData { constructor() {