mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-08 00:08:14 +01:00
225 lines
5.8 KiB
JavaScript
225 lines
5.8 KiB
JavaScript
const { DataTypes, Model } = require('sequelize')
|
|
|
|
/**
|
|
* @typedef ChapterObject
|
|
* @property {number} id
|
|
* @property {number} start
|
|
* @property {number} end
|
|
* @property {string} title
|
|
*/
|
|
|
|
class PodcastEpisode extends Model {
|
|
constructor(values, options) {
|
|
super(values, options)
|
|
|
|
/** @type {string} */
|
|
this.id
|
|
/** @type {number} */
|
|
this.index
|
|
/** @type {string} */
|
|
this.season
|
|
/** @type {string} */
|
|
this.episode
|
|
/** @type {string} */
|
|
this.episodeType
|
|
/** @type {string} */
|
|
this.title
|
|
/** @type {string} */
|
|
this.subtitle
|
|
/** @type {string} */
|
|
this.description
|
|
/** @type {string} */
|
|
this.pubDate
|
|
/** @type {string} */
|
|
this.enclosureURL
|
|
/** @type {BigInt} */
|
|
this.enclosureSize
|
|
/** @type {string} */
|
|
this.enclosureType
|
|
/** @type {Date} */
|
|
this.publishedAt
|
|
/** @type {import('./Book').AudioFileObject} */
|
|
this.audioFile
|
|
/** @type {ChapterObject[]} */
|
|
this.chapters
|
|
/** @type {Object} */
|
|
this.extraData
|
|
/** @type {string} */
|
|
this.podcastId
|
|
/** @type {Date} */
|
|
this.createdAt
|
|
/** @type {Date} */
|
|
this.updatedAt
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {import('../utils/podcastUtils').RssPodcastEpisode} rssPodcastEpisode
|
|
* @param {string} podcastId
|
|
* @param {import('../objects/files/AudioFile')} audioFile
|
|
*/
|
|
static async createFromRssPodcastEpisode(rssPodcastEpisode, podcastId, audioFile) {
|
|
const podcastEpisode = {
|
|
index: null,
|
|
season: rssPodcastEpisode.season,
|
|
episode: rssPodcastEpisode.episode,
|
|
episodeType: rssPodcastEpisode.episodeType,
|
|
title: rssPodcastEpisode.title,
|
|
subtitle: rssPodcastEpisode.subtitle,
|
|
description: rssPodcastEpisode.description,
|
|
pubDate: rssPodcastEpisode.pubDate,
|
|
enclosureURL: rssPodcastEpisode.enclosure?.url || null,
|
|
enclosureSize: rssPodcastEpisode.enclosure?.length || null,
|
|
enclosureType: rssPodcastEpisode.enclosure?.type || null,
|
|
publishedAt: rssPodcastEpisode.publishedAt,
|
|
podcastId,
|
|
audioFile: audioFile.toJSON(),
|
|
chapters: [],
|
|
extraData: {}
|
|
}
|
|
if (rssPodcastEpisode.guid) {
|
|
podcastEpisode.extraData.guid = rssPodcastEpisode.guid
|
|
}
|
|
if (audioFile.chapters?.length) {
|
|
podcastEpisode.chapters = audioFile.chapters.map((ch) => ({ ...ch }))
|
|
}
|
|
return this.create(podcastEpisode)
|
|
}
|
|
|
|
/**
|
|
* Initialize model
|
|
* @param {import('../Database').sequelize} sequelize
|
|
*/
|
|
static init(sequelize) {
|
|
super.init(
|
|
{
|
|
id: {
|
|
type: DataTypes.UUID,
|
|
defaultValue: DataTypes.UUIDV4,
|
|
primaryKey: true
|
|
},
|
|
index: DataTypes.INTEGER,
|
|
season: DataTypes.STRING,
|
|
episode: DataTypes.STRING,
|
|
episodeType: DataTypes.STRING,
|
|
title: DataTypes.STRING,
|
|
subtitle: DataTypes.STRING(1000),
|
|
description: DataTypes.TEXT,
|
|
pubDate: DataTypes.STRING,
|
|
enclosureURL: DataTypes.STRING,
|
|
enclosureSize: DataTypes.BIGINT,
|
|
enclosureType: DataTypes.STRING,
|
|
publishedAt: DataTypes.DATE,
|
|
|
|
audioFile: DataTypes.JSON,
|
|
chapters: DataTypes.JSON,
|
|
extraData: DataTypes.JSON
|
|
},
|
|
{
|
|
sequelize,
|
|
modelName: 'podcastEpisode',
|
|
indexes: [
|
|
{
|
|
name: 'podcastEpisode_createdAt_podcastId',
|
|
fields: ['createdAt', 'podcastId']
|
|
}
|
|
]
|
|
}
|
|
)
|
|
|
|
const { podcast } = sequelize.models
|
|
podcast.hasMany(PodcastEpisode, {
|
|
onDelete: 'CASCADE'
|
|
})
|
|
PodcastEpisode.belongsTo(podcast)
|
|
}
|
|
|
|
get size() {
|
|
return this.audioFile?.metadata.size || 0
|
|
}
|
|
|
|
get duration() {
|
|
return this.audioFile?.duration || 0
|
|
}
|
|
|
|
/**
|
|
* Used for matching the episode with an episode in the RSS feed
|
|
*
|
|
* @param {string} guid
|
|
* @param {string} enclosureURL
|
|
* @returns {boolean}
|
|
*/
|
|
checkMatchesGuidOrEnclosureUrl(guid, enclosureURL) {
|
|
if (this.extraData?.guid && this.extraData.guid === guid) {
|
|
return true
|
|
}
|
|
if (this.enclosureURL && this.enclosureURL === enclosureURL) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Used in client players
|
|
*
|
|
* @param {string} libraryItemId
|
|
* @returns {import('./Book').AudioTrack}
|
|
*/
|
|
getAudioTrack(libraryItemId) {
|
|
const track = structuredClone(this.audioFile)
|
|
track.startOffset = 0
|
|
track.title = this.audioFile.metadata.filename
|
|
track.contentUrl = `${global.RouterBasePath}/api/items/${libraryItemId}/file/${track.ino}`
|
|
return track
|
|
}
|
|
|
|
toOldJSON(libraryItemId) {
|
|
if (!libraryItemId) {
|
|
throw new Error(`[PodcastEpisode] Cannot convert to old JSON because libraryItemId is not provided`)
|
|
}
|
|
|
|
let enclosure = null
|
|
if (this.enclosureURL) {
|
|
enclosure = {
|
|
url: this.enclosureURL,
|
|
type: this.enclosureType,
|
|
length: this.enclosureSize !== null ? String(this.enclosureSize) : null
|
|
}
|
|
}
|
|
|
|
return {
|
|
libraryItemId: libraryItemId,
|
|
podcastId: this.podcastId,
|
|
id: this.id,
|
|
oldEpisodeId: this.extraData?.oldEpisodeId || null,
|
|
index: this.index,
|
|
season: this.season,
|
|
episode: this.episode,
|
|
episodeType: this.episodeType,
|
|
title: this.title,
|
|
subtitle: this.subtitle,
|
|
description: this.description,
|
|
enclosure,
|
|
guid: this.extraData?.guid || null,
|
|
pubDate: this.pubDate,
|
|
chapters: structuredClone(this.chapters),
|
|
audioFile: structuredClone(this.audioFile),
|
|
publishedAt: this.publishedAt?.valueOf() || null,
|
|
addedAt: this.createdAt.valueOf(),
|
|
updatedAt: this.updatedAt.valueOf()
|
|
}
|
|
}
|
|
|
|
toOldJSONExpanded(libraryItemId) {
|
|
const json = this.toOldJSON(libraryItemId)
|
|
|
|
json.audioTrack = this.getAudioTrack(libraryItemId)
|
|
json.size = this.size
|
|
json.duration = this.duration
|
|
|
|
return json
|
|
}
|
|
}
|
|
|
|
module.exports = PodcastEpisode
|