mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-03 00:06:46 +01:00
Fix:Series and collection RSS feeds keeping correct order #3137
This commit is contained in:
parent
c5e60d30e1
commit
0ee3b89760
@ -1,7 +1,9 @@
|
|||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const uuidv4 = require("uuid").v4
|
const uuidv4 = require('uuid').v4
|
||||||
const FeedMeta = require('./FeedMeta')
|
const FeedMeta = require('./FeedMeta')
|
||||||
const FeedEpisode = require('./FeedEpisode')
|
const FeedEpisode = require('./FeedEpisode')
|
||||||
|
|
||||||
|
const date = require('../libs/dateAndTime')
|
||||||
const RSS = require('../libs/rss')
|
const RSS = require('../libs/rss')
|
||||||
const { createNewSortInstance } = require('../libs/fastSort')
|
const { createNewSortInstance } = require('../libs/fastSort')
|
||||||
const naturalSort = createNewSortInstance({
|
const naturalSort = createNewSortInstance({
|
||||||
@ -46,7 +48,7 @@ class Feed {
|
|||||||
this.serverAddress = feed.serverAddress
|
this.serverAddress = feed.serverAddress
|
||||||
this.feedUrl = feed.feedUrl
|
this.feedUrl = feed.feedUrl
|
||||||
this.meta = new FeedMeta(feed.meta)
|
this.meta = new FeedMeta(feed.meta)
|
||||||
this.episodes = feed.episodes.map(ep => new FeedEpisode(ep))
|
this.episodes = feed.episodes.map((ep) => new FeedEpisode(ep))
|
||||||
this.createdAt = feed.createdAt
|
this.createdAt = feed.createdAt
|
||||||
this.updatedAt = feed.updatedAt
|
this.updatedAt = feed.updatedAt
|
||||||
}
|
}
|
||||||
@ -62,7 +64,7 @@ class Feed {
|
|||||||
serverAddress: this.serverAddress,
|
serverAddress: this.serverAddress,
|
||||||
feedUrl: this.feedUrl,
|
feedUrl: this.feedUrl,
|
||||||
meta: this.meta.toJSON(),
|
meta: this.meta.toJSON(),
|
||||||
episodes: this.episodes.map(ep => ep.toJSON()),
|
episodes: this.episodes.map((ep) => ep.toJSON()),
|
||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
updatedAt: this.updatedAt
|
updatedAt: this.updatedAt
|
||||||
}
|
}
|
||||||
@ -74,20 +76,20 @@ class Feed {
|
|||||||
entityType: this.entityType,
|
entityType: this.entityType,
|
||||||
entityId: this.entityId,
|
entityId: this.entityId,
|
||||||
feedUrl: this.feedUrl,
|
feedUrl: this.feedUrl,
|
||||||
meta: this.meta.toJSONMinified(),
|
meta: this.meta.toJSONMinified()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getEpisodePath(id) {
|
getEpisodePath(id) {
|
||||||
var episode = this.episodes.find(ep => ep.id === id)
|
var episode = this.episodes.find((ep) => ep.id === id)
|
||||||
if (!episode) return null
|
if (!episode) return null
|
||||||
return episode.fullPath
|
return episode.fullPath
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If chapters for an audiobook match the audio tracks then use chapter titles instead of audio file names
|
* If chapters for an audiobook match the audio tracks then use chapter titles instead of audio file names
|
||||||
*
|
*
|
||||||
* @param {import('../objects/LibraryItem')} libraryItem
|
* @param {import('../objects/LibraryItem')} libraryItem
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
checkUseChapterTitlesForEpisodes(libraryItem) {
|
checkUseChapterTitlesForEpisodes(libraryItem) {
|
||||||
@ -137,7 +139,8 @@ class Feed {
|
|||||||
this.meta.ownerEmail = ownerEmail
|
this.meta.ownerEmail = ownerEmail
|
||||||
|
|
||||||
this.episodes = []
|
this.episodes = []
|
||||||
if (isPodcast) { // PODCAST EPISODES
|
if (isPodcast) {
|
||||||
|
// PODCAST EPISODES
|
||||||
media.episodes.forEach((episode) => {
|
media.episodes.forEach((episode) => {
|
||||||
if (episode.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = episode.updatedAt
|
if (episode.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = episode.updatedAt
|
||||||
|
|
||||||
@ -145,7 +148,8 @@ class Feed {
|
|||||||
feedEpisode.setFromPodcastEpisode(libraryItem, serverAddress, slug, episode, this.meta)
|
feedEpisode.setFromPodcastEpisode(libraryItem, serverAddress, slug, episode, this.meta)
|
||||||
this.episodes.push(feedEpisode)
|
this.episodes.push(feedEpisode)
|
||||||
})
|
})
|
||||||
} else { // AUDIOBOOK EPISODES
|
} else {
|
||||||
|
// AUDIOBOOK EPISODES
|
||||||
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(libraryItem)
|
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(libraryItem)
|
||||||
media.tracks.forEach((audioTrack) => {
|
media.tracks.forEach((audioTrack) => {
|
||||||
const feedEpisode = new FeedEpisode()
|
const feedEpisode = new FeedEpisode()
|
||||||
@ -178,7 +182,8 @@ class Feed {
|
|||||||
this.meta.language = mediaMetadata.language
|
this.meta.language = mediaMetadata.language
|
||||||
|
|
||||||
this.episodes = []
|
this.episodes = []
|
||||||
if (isPodcast) { // PODCAST EPISODES
|
if (isPodcast) {
|
||||||
|
// PODCAST EPISODES
|
||||||
media.episodes.forEach((episode) => {
|
media.episodes.forEach((episode) => {
|
||||||
if (episode.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = episode.updatedAt
|
if (episode.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = episode.updatedAt
|
||||||
|
|
||||||
@ -186,7 +191,8 @@ class Feed {
|
|||||||
feedEpisode.setFromPodcastEpisode(libraryItem, this.serverAddress, this.slug, episode, this.meta)
|
feedEpisode.setFromPodcastEpisode(libraryItem, this.serverAddress, this.slug, episode, this.meta)
|
||||||
this.episodes.push(feedEpisode)
|
this.episodes.push(feedEpisode)
|
||||||
})
|
})
|
||||||
} else { // AUDIOBOOK EPISODES
|
} else {
|
||||||
|
// AUDIOBOOK EPISODES
|
||||||
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(libraryItem)
|
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(libraryItem)
|
||||||
media.tracks.forEach((audioTrack) => {
|
media.tracks.forEach((audioTrack) => {
|
||||||
const feedEpisode = new FeedEpisode()
|
const feedEpisode = new FeedEpisode()
|
||||||
@ -202,8 +208,8 @@ class Feed {
|
|||||||
setFromCollection(userId, slug, collectionExpanded, serverAddress, preventIndexing = true, ownerName = null, ownerEmail = null) {
|
setFromCollection(userId, slug, collectionExpanded, serverAddress, preventIndexing = true, ownerName = null, ownerEmail = null) {
|
||||||
const feedUrl = `${serverAddress}/feed/${slug}`
|
const feedUrl = `${serverAddress}/feed/${slug}`
|
||||||
|
|
||||||
const itemsWithTracks = collectionExpanded.books.filter(libraryItem => libraryItem.media.tracks.length)
|
const itemsWithTracks = collectionExpanded.books.filter((libraryItem) => libraryItem.media.tracks.length)
|
||||||
const firstItemWithCover = itemsWithTracks.find(item => item.media.coverPath)
|
const firstItemWithCover = itemsWithTracks.find((item) => item.media.coverPath)
|
||||||
|
|
||||||
this.id = uuidv4()
|
this.id = uuidv4()
|
||||||
this.slug = slug
|
this.slug = slug
|
||||||
@ -224,20 +230,28 @@ class Feed {
|
|||||||
this.meta.imageUrl = this.coverPath ? `${serverAddress}/feed/${slug}/cover${coverFileExtension}` : `${serverAddress}/Logo.png`
|
this.meta.imageUrl = this.coverPath ? `${serverAddress}/feed/${slug}/cover${coverFileExtension}` : `${serverAddress}/Logo.png`
|
||||||
this.meta.feedUrl = feedUrl
|
this.meta.feedUrl = feedUrl
|
||||||
this.meta.link = `${serverAddress}/collection/${collectionExpanded.id}`
|
this.meta.link = `${serverAddress}/collection/${collectionExpanded.id}`
|
||||||
this.meta.explicit = !!itemsWithTracks.some(li => li.media.metadata.explicit) // explicit if any item is explicit
|
this.meta.explicit = !!itemsWithTracks.some((li) => li.media.metadata.explicit) // explicit if any item is explicit
|
||||||
this.meta.preventIndexing = preventIndexing
|
this.meta.preventIndexing = preventIndexing
|
||||||
this.meta.ownerName = ownerName
|
this.meta.ownerName = ownerName
|
||||||
this.meta.ownerEmail = ownerEmail
|
this.meta.ownerEmail = ownerEmail
|
||||||
|
|
||||||
this.episodes = []
|
this.episodes = []
|
||||||
|
|
||||||
|
// Used for calculating pubdate
|
||||||
|
const earliestItemAddedAt = itemsWithTracks.reduce((earliest, item) => (item.addedAt < earliest ? item.addedAt : earliest), itemsWithTracks[0].addedAt)
|
||||||
|
|
||||||
itemsWithTracks.forEach((item, index) => {
|
itemsWithTracks.forEach((item, index) => {
|
||||||
if (item.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = item.updatedAt
|
if (item.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = item.updatedAt
|
||||||
|
|
||||||
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(item)
|
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(item)
|
||||||
item.media.tracks.forEach((audioTrack) => {
|
item.media.tracks.forEach((audioTrack) => {
|
||||||
const feedEpisode = new FeedEpisode()
|
const feedEpisode = new FeedEpisode()
|
||||||
feedEpisode.setFromAudiobookTrack(item, serverAddress, slug, audioTrack, this.meta, useChapterTitles, index)
|
|
||||||
|
// Offset pubdate to ensure correct order
|
||||||
|
let trackTimeOffset = isNaN(audioTrack.index) ? 0 : Number(audioTrack.index) * 1000 // Offset track
|
||||||
|
trackTimeOffset += index * 1000 // Offset item
|
||||||
|
const episodePubDateOverride = date.format(new Date(earliestItemAddedAt + trackTimeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
|
||||||
|
feedEpisode.setFromAudiobookTrack(item, serverAddress, slug, audioTrack, this.meta, useChapterTitles, episodePubDateOverride)
|
||||||
this.episodes.push(feedEpisode)
|
this.episodes.push(feedEpisode)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -247,8 +261,8 @@ class Feed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateFromCollection(collectionExpanded) {
|
updateFromCollection(collectionExpanded) {
|
||||||
const itemsWithTracks = collectionExpanded.books.filter(libraryItem => libraryItem.media.tracks.length)
|
const itemsWithTracks = collectionExpanded.books.filter((libraryItem) => libraryItem.media.tracks.length)
|
||||||
const firstItemWithCover = itemsWithTracks.find(item => item.media.coverPath)
|
const firstItemWithCover = itemsWithTracks.find((item) => item.media.coverPath)
|
||||||
|
|
||||||
this.entityUpdatedAt = collectionExpanded.lastUpdate
|
this.entityUpdatedAt = collectionExpanded.lastUpdate
|
||||||
this.coverPath = firstItemWithCover?.coverPath || null
|
this.coverPath = firstItemWithCover?.coverPath || null
|
||||||
@ -259,17 +273,25 @@ class Feed {
|
|||||||
this.meta.description = collectionExpanded.description || ''
|
this.meta.description = collectionExpanded.description || ''
|
||||||
this.meta.author = this.getAuthorsStringFromLibraryItems(itemsWithTracks)
|
this.meta.author = this.getAuthorsStringFromLibraryItems(itemsWithTracks)
|
||||||
this.meta.imageUrl = this.coverPath ? `${this.serverAddress}/feed/${this.slug}/cover${coverFileExtension}` : `${this.serverAddress}/Logo.png`
|
this.meta.imageUrl = this.coverPath ? `${this.serverAddress}/feed/${this.slug}/cover${coverFileExtension}` : `${this.serverAddress}/Logo.png`
|
||||||
this.meta.explicit = !!itemsWithTracks.some(li => li.media.metadata.explicit) // explicit if any item is explicit
|
this.meta.explicit = !!itemsWithTracks.some((li) => li.media.metadata.explicit) // explicit if any item is explicit
|
||||||
|
|
||||||
this.episodes = []
|
this.episodes = []
|
||||||
|
|
||||||
|
// Used for calculating pubdate
|
||||||
|
const earliestItemAddedAt = itemsWithTracks.reduce((earliest, item) => (item.addedAt < earliest ? item.addedAt : earliest), itemsWithTracks[0].addedAt)
|
||||||
|
|
||||||
itemsWithTracks.forEach((item, index) => {
|
itemsWithTracks.forEach((item, index) => {
|
||||||
if (item.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = item.updatedAt
|
if (item.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = item.updatedAt
|
||||||
|
|
||||||
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(item)
|
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(item)
|
||||||
item.media.tracks.forEach((audioTrack) => {
|
item.media.tracks.forEach((audioTrack) => {
|
||||||
const feedEpisode = new FeedEpisode()
|
const feedEpisode = new FeedEpisode()
|
||||||
feedEpisode.setFromAudiobookTrack(item, this.serverAddress, this.slug, audioTrack, this.meta, useChapterTitles, index)
|
|
||||||
|
// Offset pubdate to ensure correct order
|
||||||
|
let trackTimeOffset = isNaN(audioTrack.index) ? 0 : Number(audioTrack.index) * 1000 // Offset track
|
||||||
|
trackTimeOffset += index * 1000 // Offset item
|
||||||
|
const episodePubDateOverride = date.format(new Date(earliestItemAddedAt + trackTimeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
|
||||||
|
feedEpisode.setFromAudiobookTrack(item, this.serverAddress, this.slug, audioTrack, this.meta, useChapterTitles, episodePubDateOverride)
|
||||||
this.episodes.push(feedEpisode)
|
this.episodes.push(feedEpisode)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -281,12 +303,12 @@ class Feed {
|
|||||||
setFromSeries(userId, slug, seriesExpanded, serverAddress, preventIndexing = true, ownerName = null, ownerEmail = null) {
|
setFromSeries(userId, slug, seriesExpanded, serverAddress, preventIndexing = true, ownerName = null, ownerEmail = null) {
|
||||||
const feedUrl = `${serverAddress}/feed/${slug}`
|
const feedUrl = `${serverAddress}/feed/${slug}`
|
||||||
|
|
||||||
let itemsWithTracks = seriesExpanded.books.filter(libraryItem => libraryItem.media.tracks.length)
|
let itemsWithTracks = seriesExpanded.books.filter((libraryItem) => libraryItem.media.tracks.length)
|
||||||
// Sort series items by series sequence
|
// Sort series items by series sequence
|
||||||
itemsWithTracks = naturalSort(itemsWithTracks).asc(li => li.media.metadata.getSeriesSequence(seriesExpanded.id))
|
itemsWithTracks = naturalSort(itemsWithTracks).asc((li) => li.media.metadata.getSeriesSequence(seriesExpanded.id))
|
||||||
|
|
||||||
const libraryId = itemsWithTracks[0].libraryId
|
const libraryId = itemsWithTracks[0].libraryId
|
||||||
const firstItemWithCover = itemsWithTracks.find(li => li.media.coverPath)
|
const firstItemWithCover = itemsWithTracks.find((li) => li.media.coverPath)
|
||||||
|
|
||||||
this.id = uuidv4()
|
this.id = uuidv4()
|
||||||
this.slug = slug
|
this.slug = slug
|
||||||
@ -307,20 +329,28 @@ class Feed {
|
|||||||
this.meta.imageUrl = this.coverPath ? `${serverAddress}/feed/${slug}/cover${coverFileExtension}` : `${serverAddress}/Logo.png`
|
this.meta.imageUrl = this.coverPath ? `${serverAddress}/feed/${slug}/cover${coverFileExtension}` : `${serverAddress}/Logo.png`
|
||||||
this.meta.feedUrl = feedUrl
|
this.meta.feedUrl = feedUrl
|
||||||
this.meta.link = `${serverAddress}/library/${libraryId}/series/${seriesExpanded.id}`
|
this.meta.link = `${serverAddress}/library/${libraryId}/series/${seriesExpanded.id}`
|
||||||
this.meta.explicit = !!itemsWithTracks.some(li => li.media.metadata.explicit) // explicit if any item is explicit
|
this.meta.explicit = !!itemsWithTracks.some((li) => li.media.metadata.explicit) // explicit if any item is explicit
|
||||||
this.meta.preventIndexing = preventIndexing
|
this.meta.preventIndexing = preventIndexing
|
||||||
this.meta.ownerName = ownerName
|
this.meta.ownerName = ownerName
|
||||||
this.meta.ownerEmail = ownerEmail
|
this.meta.ownerEmail = ownerEmail
|
||||||
|
|
||||||
this.episodes = []
|
this.episodes = []
|
||||||
|
|
||||||
|
// Used for calculating pubdate
|
||||||
|
const earliestItemAddedAt = itemsWithTracks.reduce((earliest, item) => (item.addedAt < earliest ? item.addedAt : earliest), itemsWithTracks[0].addedAt)
|
||||||
|
|
||||||
itemsWithTracks.forEach((item, index) => {
|
itemsWithTracks.forEach((item, index) => {
|
||||||
if (item.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = item.updatedAt
|
if (item.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = item.updatedAt
|
||||||
|
|
||||||
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(item)
|
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(item)
|
||||||
item.media.tracks.forEach((audioTrack) => {
|
item.media.tracks.forEach((audioTrack) => {
|
||||||
const feedEpisode = new FeedEpisode()
|
const feedEpisode = new FeedEpisode()
|
||||||
feedEpisode.setFromAudiobookTrack(item, serverAddress, slug, audioTrack, this.meta, useChapterTitles, index)
|
|
||||||
|
// Offset pubdate to ensure correct order
|
||||||
|
let trackTimeOffset = isNaN(audioTrack.index) ? 0 : Number(audioTrack.index) * 1000 // Offset track
|
||||||
|
trackTimeOffset += index * 1000 // Offset item
|
||||||
|
const episodePubDateOverride = date.format(new Date(earliestItemAddedAt + trackTimeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
|
||||||
|
feedEpisode.setFromAudiobookTrack(item, serverAddress, slug, audioTrack, this.meta, useChapterTitles, episodePubDateOverride)
|
||||||
this.episodes.push(feedEpisode)
|
this.episodes.push(feedEpisode)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -330,11 +360,11 @@ class Feed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateFromSeries(seriesExpanded) {
|
updateFromSeries(seriesExpanded) {
|
||||||
let itemsWithTracks = seriesExpanded.books.filter(libraryItem => libraryItem.media.tracks.length)
|
let itemsWithTracks = seriesExpanded.books.filter((libraryItem) => libraryItem.media.tracks.length)
|
||||||
// Sort series items by series sequence
|
// Sort series items by series sequence
|
||||||
itemsWithTracks = naturalSort(itemsWithTracks).asc(li => li.media.metadata.getSeriesSequence(seriesExpanded.id))
|
itemsWithTracks = naturalSort(itemsWithTracks).asc((li) => li.media.metadata.getSeriesSequence(seriesExpanded.id))
|
||||||
|
|
||||||
const firstItemWithCover = itemsWithTracks.find(item => item.media.coverPath)
|
const firstItemWithCover = itemsWithTracks.find((item) => item.media.coverPath)
|
||||||
|
|
||||||
this.entityUpdatedAt = seriesExpanded.updatedAt
|
this.entityUpdatedAt = seriesExpanded.updatedAt
|
||||||
this.coverPath = firstItemWithCover?.coverPath || null
|
this.coverPath = firstItemWithCover?.coverPath || null
|
||||||
@ -345,17 +375,25 @@ class Feed {
|
|||||||
this.meta.description = seriesExpanded.description || ''
|
this.meta.description = seriesExpanded.description || ''
|
||||||
this.meta.author = this.getAuthorsStringFromLibraryItems(itemsWithTracks)
|
this.meta.author = this.getAuthorsStringFromLibraryItems(itemsWithTracks)
|
||||||
this.meta.imageUrl = this.coverPath ? `${this.serverAddress}/feed/${this.slug}/cover${coverFileExtension}` : `${this.serverAddress}/Logo.png`
|
this.meta.imageUrl = this.coverPath ? `${this.serverAddress}/feed/${this.slug}/cover${coverFileExtension}` : `${this.serverAddress}/Logo.png`
|
||||||
this.meta.explicit = !!itemsWithTracks.some(li => li.media.metadata.explicit) // explicit if any item is explicit
|
this.meta.explicit = !!itemsWithTracks.some((li) => li.media.metadata.explicit) // explicit if any item is explicit
|
||||||
|
|
||||||
this.episodes = []
|
this.episodes = []
|
||||||
|
|
||||||
|
// Used for calculating pubdate
|
||||||
|
const earliestItemAddedAt = itemsWithTracks.reduce((earliest, item) => (item.addedAt < earliest ? item.addedAt : earliest), itemsWithTracks[0].addedAt)
|
||||||
|
|
||||||
itemsWithTracks.forEach((item, index) => {
|
itemsWithTracks.forEach((item, index) => {
|
||||||
if (item.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = item.updatedAt
|
if (item.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = item.updatedAt
|
||||||
|
|
||||||
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(item)
|
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(item)
|
||||||
item.media.tracks.forEach((audioTrack) => {
|
item.media.tracks.forEach((audioTrack) => {
|
||||||
const feedEpisode = new FeedEpisode()
|
const feedEpisode = new FeedEpisode()
|
||||||
feedEpisode.setFromAudiobookTrack(item, this.serverAddress, this.slug, audioTrack, this.meta, useChapterTitles, index)
|
|
||||||
|
// Offset pubdate to ensure correct order
|
||||||
|
let trackTimeOffset = isNaN(audioTrack.index) ? 0 : Number(audioTrack.index) * 1000 // Offset track
|
||||||
|
trackTimeOffset += index * 1000 // Offset item
|
||||||
|
const episodePubDateOverride = date.format(new Date(earliestItemAddedAt + trackTimeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
|
||||||
|
feedEpisode.setFromAudiobookTrack(item, this.serverAddress, this.slug, audioTrack, this.meta, useChapterTitles, episodePubDateOverride)
|
||||||
this.episodes.push(feedEpisode)
|
this.episodes.push(feedEpisode)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -377,7 +415,7 @@ class Feed {
|
|||||||
|
|
||||||
getAuthorsStringFromLibraryItems(libraryItems) {
|
getAuthorsStringFromLibraryItems(libraryItems) {
|
||||||
let itemAuthors = []
|
let itemAuthors = []
|
||||||
libraryItems.forEach((item) => itemAuthors.push(...item.media.metadata.authors.map(au => au.name)))
|
libraryItems.forEach((item) => itemAuthors.push(...item.media.metadata.authors.map((au) => au.name)))
|
||||||
itemAuthors = [...new Set(itemAuthors)] // Filter out dupes
|
itemAuthors = [...new Set(itemAuthors)] // Filter out dupes
|
||||||
let author = itemAuthors.slice(0, 3).join(', ')
|
let author = itemAuthors.slice(0, 3).join(', ')
|
||||||
if (itemAuthors.length > 3) {
|
if (itemAuthors.length > 3) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const uuidv4 = require("uuid").v4
|
const uuidv4 = require('uuid').v4
|
||||||
const date = require('../libs/dateAndTime')
|
const date = require('../libs/dateAndTime')
|
||||||
const { secondsToTimestamp } = require('../utils/index')
|
const { secondsToTimestamp } = require('../utils/index')
|
||||||
|
|
||||||
@ -98,27 +98,22 @@ class FeedEpisode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import('../objects/LibraryItem')} libraryItem
|
* @param {import('../objects/LibraryItem')} libraryItem
|
||||||
* @param {string} serverAddress
|
* @param {string} serverAddress
|
||||||
* @param {string} slug
|
* @param {string} slug
|
||||||
* @param {import('../objects/files/AudioTrack')} audioTrack
|
* @param {import('../objects/files/AudioTrack')} audioTrack
|
||||||
* @param {Object} meta
|
* @param {Object} meta
|
||||||
* @param {boolean} useChapterTitles
|
* @param {boolean} useChapterTitles
|
||||||
* @param {number} [additionalOffset]
|
* @param {string} [pubDateOverride] Used for series & collections to ensure correct episode order
|
||||||
*/
|
*/
|
||||||
setFromAudiobookTrack(libraryItem, serverAddress, slug, audioTrack, meta, useChapterTitles, additionalOffset = null) {
|
setFromAudiobookTrack(libraryItem, serverAddress, slug, audioTrack, meta, useChapterTitles, pubDateOverride = null) {
|
||||||
// Example: <pubDate>Fri, 04 Feb 2015 00:00:00 GMT</pubDate>
|
// 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 timeOffset = isNaN(audioTrack.index) ? 0 : Number(audioTrack.index) * 1000 // Offset pubdate to ensure correct order
|
||||||
let episodeId = uuidv4()
|
let episodeId = uuidv4()
|
||||||
|
|
||||||
// Additional offset can be used for collections/series
|
|
||||||
if (additionalOffset !== null && !isNaN(additionalOffset)) {
|
|
||||||
timeOffset += Number(additionalOffset) * 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
// e.g. Track 1 will have a pub date before Track 2
|
// e.g. Track 1 will have a pub date before Track 2
|
||||||
const audiobookPubDate = date.format(new Date(libraryItem.addedAt + timeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
|
const audiobookPubDate = pubDateOverride || date.format(new Date(libraryItem.addedAt + timeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
|
||||||
|
|
||||||
const contentFileExtension = Path.extname(audioTrack.metadata.filename)
|
const contentFileExtension = Path.extname(audioTrack.metadata.filename)
|
||||||
const contentUrl = `/feed/${slug}/item/${episodeId}/media${contentFileExtension}`
|
const contentUrl = `/feed/${slug}/item/${episodeId}/media${contentFileExtension}`
|
||||||
@ -126,12 +121,13 @@ class FeedEpisode {
|
|||||||
const mediaMetadata = media.metadata
|
const mediaMetadata = media.metadata
|
||||||
|
|
||||||
let title = audioTrack.title
|
let title = audioTrack.title
|
||||||
if (libraryItem.media.tracks.length == 1) { // If audiobook is a single file, use book title instead of chapter/file title
|
if (libraryItem.media.tracks.length == 1) {
|
||||||
|
// If audiobook is a single file, use book title instead of chapter/file title
|
||||||
title = libraryItem.media.metadata.title
|
title = libraryItem.media.metadata.title
|
||||||
} else {
|
} else {
|
||||||
if (useChapterTitles) {
|
if (useChapterTitles) {
|
||||||
// If audio track start and chapter start are within 1 seconds of eachother then use the chapter title
|
// If audio track start and chapter start are within 1 seconds of eachother then use the chapter title
|
||||||
const matchingChapter = libraryItem.media.chapters.find(ch => Math.abs(ch.start - audioTrack.startOffset) < 1)
|
const matchingChapter = libraryItem.media.chapters.find((ch) => Math.abs(ch.start - audioTrack.startOffset) < 1)
|
||||||
if (matchingChapter?.title) title = matchingChapter.title
|
if (matchingChapter?.title) title = matchingChapter.title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,11 +165,11 @@ class FeedEpisode {
|
|||||||
{ 'itunes:duration': secondsToTimestamp(this.duration) },
|
{ 'itunes:duration': secondsToTimestamp(this.duration) },
|
||||||
{ 'itunes:summary': this.description || '' },
|
{ 'itunes:summary': this.description || '' },
|
||||||
{
|
{
|
||||||
"itunes:explicit": !!this.explicit
|
'itunes:explicit': !!this.explicit
|
||||||
},
|
},
|
||||||
{ "itunes:episodeType": this.episodeType },
|
{ 'itunes:episodeType': this.episodeType },
|
||||||
{ "itunes:season": this.season },
|
{ 'itunes:season': this.season },
|
||||||
{ "itunes:episode": this.episode }
|
{ 'itunes:episode': this.episode }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user