Refactor Feed model to create new feed for collection

This commit is contained in:
advplyr
2024-12-15 10:53:31 -06:00
parent ca2327aba3
commit d576625cb7
7 changed files with 171 additions and 93 deletions

View File

@@ -132,12 +132,12 @@ class FeedEpisode extends Model {
/**
* If chapters for an audiobook match the audio tracks then use chapter titles instead of audio file names
*
* @param {import('./LibraryItem').LibraryItemExpanded} libraryItemExpanded
* @param {import('./Book')} book
* @returns {boolean}
*/
static checkUseChapterTitlesForEpisodes(libraryItemExpanded) {
const tracks = libraryItemExpanded.media.trackList || []
const chapters = libraryItemExpanded.media.chapters || []
static checkUseChapterTitlesForEpisodes(book) {
const tracks = book.trackList || []
const chapters = book.chapters || []
if (tracks.length !== chapters.length) return false
for (let i = 0; i < tracks.length; i++) {
if (Math.abs(chapters[i].start - tracks[i].startOffset) >= 1) {
@@ -149,32 +149,31 @@ class FeedEpisode extends Model {
/**
*
* @param {import('./LibraryItem').LibraryItemExpanded} libraryItemExpanded
* @param {import('./Book')} book
* @param {Date} pubDateStart
* @param {import('./Feed')} feed
* @param {string} slug
* @param {import('./Book').AudioFileObject} audioTrack
* @param {boolean} useChapterTitles
* @param {string} [pubDateOverride]
*/
static getFeedEpisodeObjFromAudiobookTrack(libraryItemExpanded, feed, slug, audioTrack, useChapterTitles, pubDateOverride = null) {
static getFeedEpisodeObjFromAudiobookTrack(book, pubDateStart, feed, slug, audioTrack, useChapterTitles) {
// Example: <pubDate>Fri, 04 Feb 2015 00:00:00 GMT</pubDate>
let timeOffset = isNaN(audioTrack.index) ? 0 : Number(audioTrack.index) * 1000 // Offset pubdate to ensure correct order
let episodeId = uuidv4()
// e.g. Track 1 will have a pub date before Track 2
const audiobookPubDate = pubDateOverride || date.format(new Date(libraryItemExpanded.createdAt.valueOf() + timeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
const audiobookPubDate = date.format(new Date(pubDateStart.valueOf() + timeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
const contentUrl = `/feed/${slug}/item/${episodeId}/media${Path.extname(audioTrack.metadata.filename)}`
const media = libraryItemExpanded.media
let title = audioTrack.title
if (media.trackList.length == 1) {
if (book.trackList.length == 1) {
// If audiobook is a single file, use book title instead of chapter/file title
title = media.title
title = book.title
} else {
if (useChapterTitles) {
// If audio track start and chapter start are within 1 seconds of eachother then use the chapter title
const matchingChapter = media.chapters.find((ch) => Math.abs(ch.start - audioTrack.startOffset) < 1)
const matchingChapter = book.chapters.find((ch) => Math.abs(ch.start - audioTrack.startOffset) < 1)
if (matchingChapter?.title) title = matchingChapter.title
}
}
@@ -183,7 +182,7 @@ class FeedEpisode extends Model {
id: episodeId,
title,
author: feed.author,
description: media.description || '',
description: book.description || '',
siteURL: feed.siteURL,
enclosureURL: contentUrl,
enclosureType: audioTrack.mimeType,
@@ -191,7 +190,7 @@ class FeedEpisode extends Model {
pubDate: audiobookPubDate,
duration: audioTrack.duration,
filePath: audioTrack.metadata.path,
explicit: media.explicit,
explicit: book.explicit,
feedId: feed.id
}
}
@@ -205,11 +204,37 @@ class FeedEpisode extends Model {
* @returns {Promise<FeedEpisode[]>}
*/
static async createFromAudiobookTracks(libraryItemExpanded, feed, slug, transaction) {
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(libraryItemExpanded)
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(libraryItemExpanded.media)
const feedEpisodeObjs = []
for (const track of libraryItemExpanded.media.trackList) {
feedEpisodeObjs.push(this.getFeedEpisodeObjFromAudiobookTrack(libraryItemExpanded, feed, slug, track, useChapterTitles))
feedEpisodeObjs.push(this.getFeedEpisodeObjFromAudiobookTrack(libraryItemExpanded.media, libraryItemExpanded.createdAt, feed, slug, track, useChapterTitles))
}
Logger.info(`[FeedEpisode] Creating ${feedEpisodeObjs.length} episodes for feed ${feed.id}`)
return this.bulkCreate(feedEpisodeObjs, { transaction })
}
/**
*
* @param {import('./Collection')} collectionExpanded
* @param {import('./Feed')} feed
* @param {string} slug
* @param {import('sequelize').Transaction} transaction
* @returns {Promise<FeedEpisode[]>}
*/
static async createFromCollectionBooks(collectionExpanded, feed, slug, transaction) {
const booksWithTracks = collectionExpanded.books.filter((book) => book.includedAudioFiles.length)
const earliestLibraryItemCreatedAt = collectionExpanded.books.reduce((earliest, book) => {
return book.libraryItem.createdAt < earliest.libraryItem.createdAt ? book : earliest
}).libraryItem.createdAt
const feedEpisodeObjs = []
for (const book of booksWithTracks) {
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(book)
for (const track of book.trackList) {
feedEpisodeObjs.push(this.getFeedEpisodeObjFromAudiobookTrack(book, earliestLibraryItemCreatedAt, feed, slug, track, useChapterTitles))
}
}
Logger.info(`[FeedEpisode] Creating ${feedEpisodeObjs.length} episodes for feed ${feed.id}`)
return this.bulkCreate(feedEpisodeObjs, { transaction })