mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-04 03:17:00 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			314 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			314 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
const { DataTypes, Model } = require('sequelize')
 | 
						|
const Logger = require('../Logger')
 | 
						|
 | 
						|
/**
 | 
						|
 * @typedef EBookFileObject
 | 
						|
 * @property {string} ino
 | 
						|
 * @property {string} ebookFormat
 | 
						|
 * @property {number} addedAt
 | 
						|
 * @property {number} updatedAt
 | 
						|
 * @property {{filename:string, ext:string, path:string, relPath:string, size:number, mtimeMs:number, ctimeMs:number, birthtimeMs:number}} metadata
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * @typedef ChapterObject
 | 
						|
 * @property {number} id
 | 
						|
 * @property {number} start
 | 
						|
 * @property {number} end
 | 
						|
 * @property {string} title
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * @typedef SeriesExpandedProperties
 | 
						|
 * @property {{sequence:string}} bookSeries
 | 
						|
 * 
 | 
						|
 * @typedef {import('./Series') & SeriesExpandedProperties} SeriesExpanded
 | 
						|
 * 
 | 
						|
 * @typedef BookExpandedProperties
 | 
						|
 * @property {import('./Author')[]} authors
 | 
						|
 * @property {SeriesExpanded[]} series
 | 
						|
 * 
 | 
						|
 * @typedef {Book & BookExpandedProperties} BookExpanded
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * @typedef AudioFileObject
 | 
						|
 * @property {number} index
 | 
						|
 * @property {string} ino
 | 
						|
 * @property {{filename:string, ext:string, path:string, relPath:string, size:number, mtimeMs:number, ctimeMs:number, birthtimeMs:number}} metadata
 | 
						|
 * @property {number} addedAt
 | 
						|
 * @property {number} updatedAt
 | 
						|
 * @property {number} trackNumFromMeta
 | 
						|
 * @property {number} discNumFromMeta
 | 
						|
 * @property {number} trackNumFromFilename
 | 
						|
 * @property {number} discNumFromFilename
 | 
						|
 * @property {boolean} manuallyVerified
 | 
						|
 * @property {string} format
 | 
						|
 * @property {number} duration
 | 
						|
 * @property {number} bitRate
 | 
						|
 * @property {string} language
 | 
						|
 * @property {string} codec
 | 
						|
 * @property {string} timeBase
 | 
						|
 * @property {number} channels
 | 
						|
 * @property {string} channelLayout
 | 
						|
 * @property {ChapterObject[]} chapters
 | 
						|
 * @property {Object} metaTags
 | 
						|
 * @property {string} mimeType
 | 
						|
 */
 | 
						|
 | 
						|
class Book extends Model {
 | 
						|
  constructor(values, options) {
 | 
						|
    super(values, options)
 | 
						|
 | 
						|
    /** @type {string} */
 | 
						|
    this.id
 | 
						|
    /** @type {string} */
 | 
						|
    this.title
 | 
						|
    /** @type {string} */
 | 
						|
    this.titleIgnorePrefix
 | 
						|
    /** @type {string} */
 | 
						|
    this.subtitle
 | 
						|
    /** @type {string} */
 | 
						|
    this.publishedYear
 | 
						|
    /** @type {string} */
 | 
						|
    this.publishedDate
 | 
						|
    /** @type {string} */
 | 
						|
    this.publisher
 | 
						|
    /** @type {string} */
 | 
						|
    this.description
 | 
						|
    /** @type {string} */
 | 
						|
    this.isbn
 | 
						|
    /** @type {string} */
 | 
						|
    this.asin
 | 
						|
    /** @type {string} */
 | 
						|
    this.language
 | 
						|
    /** @type {boolean} */
 | 
						|
    this.explicit
 | 
						|
    /** @type {boolean} */
 | 
						|
    this.abridged
 | 
						|
    /** @type {string} */
 | 
						|
    this.coverPath
 | 
						|
    /** @type {number} */
 | 
						|
    this.duration
 | 
						|
    /** @type {string[]} */
 | 
						|
    this.narrators
 | 
						|
    /** @type {AudioFileObject[]} */
 | 
						|
    this.audioFiles
 | 
						|
    /** @type {EBookFileObject} */
 | 
						|
    this.ebookFile
 | 
						|
    /** @type {ChapterObject[]} */
 | 
						|
    this.chapters
 | 
						|
    /** @type {string[]} */
 | 
						|
    this.tags
 | 
						|
    /** @type {string[]} */
 | 
						|
    this.genres
 | 
						|
    /** @type {Date} */
 | 
						|
    this.updatedAt
 | 
						|
    /** @type {Date} */
 | 
						|
    this.createdAt
 | 
						|
  }
 | 
						|
 | 
						|
  static getOldBook(libraryItemExpanded) {
 | 
						|
    const bookExpanded = libraryItemExpanded.media
 | 
						|
    let authors = []
 | 
						|
    if (bookExpanded.authors?.length) {
 | 
						|
      authors = bookExpanded.authors.map(au => {
 | 
						|
        return {
 | 
						|
          id: au.id,
 | 
						|
          name: au.name
 | 
						|
        }
 | 
						|
      })
 | 
						|
    } else if (bookExpanded.bookAuthors?.length) {
 | 
						|
      authors = bookExpanded.bookAuthors.map(ba => {
 | 
						|
        if (ba.author) {
 | 
						|
          return {
 | 
						|
            id: ba.author.id,
 | 
						|
            name: ba.author.name
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          Logger.error(`[Book] Invalid bookExpanded bookAuthors: no author`, ba)
 | 
						|
          return null
 | 
						|
        }
 | 
						|
      }).filter(a => a)
 | 
						|
    }
 | 
						|
 | 
						|
    let series = []
 | 
						|
    if (bookExpanded.series?.length) {
 | 
						|
      series = bookExpanded.series.map(se => {
 | 
						|
        return {
 | 
						|
          id: se.id,
 | 
						|
          name: se.name,
 | 
						|
          sequence: se.bookSeries.sequence
 | 
						|
        }
 | 
						|
      })
 | 
						|
    } else if (bookExpanded.bookSeries?.length) {
 | 
						|
      series = bookExpanded.bookSeries.map(bs => {
 | 
						|
        if (bs.series) {
 | 
						|
          return {
 | 
						|
            id: bs.series.id,
 | 
						|
            name: bs.series.name,
 | 
						|
            sequence: bs.sequence
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          Logger.error(`[Book] Invalid bookExpanded bookSeries: no series`, bs)
 | 
						|
          return null
 | 
						|
        }
 | 
						|
      }).filter(s => s)
 | 
						|
    }
 | 
						|
 | 
						|
    return {
 | 
						|
      id: bookExpanded.id,
 | 
						|
      libraryItemId: libraryItemExpanded.id,
 | 
						|
      coverPath: bookExpanded.coverPath,
 | 
						|
      tags: bookExpanded.tags,
 | 
						|
      audioFiles: bookExpanded.audioFiles,
 | 
						|
      chapters: bookExpanded.chapters,
 | 
						|
      ebookFile: bookExpanded.ebookFile,
 | 
						|
      metadata: {
 | 
						|
        title: bookExpanded.title,
 | 
						|
        subtitle: bookExpanded.subtitle,
 | 
						|
        authors: authors,
 | 
						|
        narrators: bookExpanded.narrators,
 | 
						|
        series: series,
 | 
						|
        genres: bookExpanded.genres,
 | 
						|
        publishedYear: bookExpanded.publishedYear,
 | 
						|
        publishedDate: bookExpanded.publishedDate,
 | 
						|
        publisher: bookExpanded.publisher,
 | 
						|
        description: bookExpanded.description,
 | 
						|
        isbn: bookExpanded.isbn,
 | 
						|
        asin: bookExpanded.asin,
 | 
						|
        language: bookExpanded.language,
 | 
						|
        explicit: bookExpanded.explicit,
 | 
						|
        abridged: bookExpanded.abridged
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * @param {object} oldBook 
 | 
						|
   * @returns {boolean} true if updated
 | 
						|
   */
 | 
						|
  static saveFromOld(oldBook) {
 | 
						|
    const book = this.getFromOld(oldBook)
 | 
						|
    return this.update(book, {
 | 
						|
      where: {
 | 
						|
        id: book.id
 | 
						|
      }
 | 
						|
    }).then(result => result[0] > 0).catch((error) => {
 | 
						|
      Logger.error(`[Book] Failed to save book ${book.id}`, error)
 | 
						|
      return false
 | 
						|
    })
 | 
						|
  }
 | 
						|
 | 
						|
  static getFromOld(oldBook) {
 | 
						|
    return {
 | 
						|
      id: oldBook.id,
 | 
						|
      title: oldBook.metadata.title,
 | 
						|
      titleIgnorePrefix: oldBook.metadata.titleIgnorePrefix,
 | 
						|
      subtitle: oldBook.metadata.subtitle,
 | 
						|
      publishedYear: oldBook.metadata.publishedYear,
 | 
						|
      publishedDate: oldBook.metadata.publishedDate,
 | 
						|
      publisher: oldBook.metadata.publisher,
 | 
						|
      description: oldBook.metadata.description,
 | 
						|
      isbn: oldBook.metadata.isbn,
 | 
						|
      asin: oldBook.metadata.asin,
 | 
						|
      language: oldBook.metadata.language,
 | 
						|
      explicit: !!oldBook.metadata.explicit,
 | 
						|
      abridged: !!oldBook.metadata.abridged,
 | 
						|
      narrators: oldBook.metadata.narrators,
 | 
						|
      ebookFile: oldBook.ebookFile?.toJSON() || null,
 | 
						|
      coverPath: oldBook.coverPath,
 | 
						|
      duration: oldBook.duration,
 | 
						|
      audioFiles: oldBook.audioFiles?.map(af => af.toJSON()) || [],
 | 
						|
      chapters: oldBook.chapters,
 | 
						|
      tags: oldBook.tags,
 | 
						|
      genres: oldBook.metadata.genres
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  getAbsMetadataJson() {
 | 
						|
    return {
 | 
						|
      tags: this.tags || [],
 | 
						|
      chapters: this.chapters?.map(c => ({ ...c })) || [],
 | 
						|
      title: this.title,
 | 
						|
      subtitle: this.subtitle,
 | 
						|
      authors: this.authors.map(a => a.name),
 | 
						|
      narrators: this.narrators,
 | 
						|
      series: this.series.map(se => {
 | 
						|
        const sequence = se.bookSeries?.sequence || ''
 | 
						|
        if (!sequence) return se.name
 | 
						|
        return `${se.name} #${sequence}`
 | 
						|
      }),
 | 
						|
      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,
 | 
						|
      abridged: !!this.abridged
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Initialize model
 | 
						|
   * @param {import('../Database').sequelize} sequelize 
 | 
						|
   */
 | 
						|
  static init(sequelize) {
 | 
						|
    super.init({
 | 
						|
      id: {
 | 
						|
        type: DataTypes.UUID,
 | 
						|
        defaultValue: DataTypes.UUIDV4,
 | 
						|
        primaryKey: true
 | 
						|
      },
 | 
						|
      title: DataTypes.STRING,
 | 
						|
      titleIgnorePrefix: DataTypes.STRING,
 | 
						|
      subtitle: DataTypes.STRING,
 | 
						|
      publishedYear: DataTypes.STRING,
 | 
						|
      publishedDate: DataTypes.STRING,
 | 
						|
      publisher: DataTypes.STRING,
 | 
						|
      description: DataTypes.TEXT,
 | 
						|
      isbn: DataTypes.STRING,
 | 
						|
      asin: DataTypes.STRING,
 | 
						|
      language: DataTypes.STRING,
 | 
						|
      explicit: DataTypes.BOOLEAN,
 | 
						|
      abridged: DataTypes.BOOLEAN,
 | 
						|
      coverPath: DataTypes.STRING,
 | 
						|
      duration: DataTypes.FLOAT,
 | 
						|
 | 
						|
      narrators: DataTypes.JSON,
 | 
						|
      audioFiles: DataTypes.JSON,
 | 
						|
      ebookFile: DataTypes.JSON,
 | 
						|
      chapters: DataTypes.JSON,
 | 
						|
      tags: DataTypes.JSON,
 | 
						|
      genres: DataTypes.JSON
 | 
						|
    }, {
 | 
						|
      sequelize,
 | 
						|
      modelName: 'book',
 | 
						|
      indexes: [
 | 
						|
        {
 | 
						|
          fields: [{
 | 
						|
            name: 'title',
 | 
						|
            collate: 'NOCASE'
 | 
						|
          }]
 | 
						|
        },
 | 
						|
        // {
 | 
						|
        //   fields: [{
 | 
						|
        //     name: 'titleIgnorePrefix',
 | 
						|
        //     collate: 'NOCASE'
 | 
						|
        //   }]
 | 
						|
        // },
 | 
						|
        {
 | 
						|
          fields: ['publishedYear']
 | 
						|
        },
 | 
						|
        // {
 | 
						|
        //   fields: ['duration']
 | 
						|
        // }
 | 
						|
      ]
 | 
						|
    })
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
module.exports = Book |