mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-22 19:59:37 +00:00
Merge branch 'advplyr:master' into scan-for-narrator
This commit is contained in:
commit
efdb43e2d2
16 changed files with 197 additions and 194 deletions
|
|
@ -86,13 +86,17 @@ class LibraryController {
|
|||
return f
|
||||
})
|
||||
for (var path of newFolderPaths) {
|
||||
var success = await fs.ensureDir(path).then(() => true).catch((error) => {
|
||||
Logger.error(`[LibraryController] Failed to ensure folder dir "${path}"`, error)
|
||||
return false
|
||||
})
|
||||
if (!success) {
|
||||
return res.status(400).send(`Invalid folder directory "${path}"`)
|
||||
} else {
|
||||
var pathExists = await fs.pathExists(path)
|
||||
if (!pathExists) {
|
||||
// Ensure dir will recursively create directories which might be preferred over mkdir
|
||||
var success = await fs.ensureDir(path).then(() => true).catch((error) => {
|
||||
Logger.error(`[LibraryController] Failed to ensure folder dir "${path}"`, error)
|
||||
return false
|
||||
})
|
||||
if (!success) {
|
||||
return res.status(400).send(`Invalid folder directory "${path}"`)
|
||||
}
|
||||
// Set permissions on newly created path
|
||||
await filePerms.setDefault(path)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -405,6 +405,38 @@ class LibraryItemController {
|
|||
})
|
||||
}
|
||||
|
||||
// POST: api/items/:id/open-feed
|
||||
async openRSSFeed(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[LibraryItemController] Non-admin user attempted to open RSS feed`, req.user.username)
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
|
||||
const feedData = this.rssFeedManager.openFeedForItem(req.user, req.libraryItem, req.body)
|
||||
if (feedData.error) {
|
||||
return res.json({
|
||||
success: false,
|
||||
error: feedData.error
|
||||
})
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
feedUrl: feedData.feedUrl
|
||||
})
|
||||
}
|
||||
|
||||
async closeRSSFeed(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[LibraryItemController] Non-admin user attempted to close RSS feed`, req.user.username)
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
|
||||
this.rssFeedManager.closeFeedForItem(req.params.id)
|
||||
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
middleware(req, res, next) {
|
||||
var item = this.db.libraryItems.find(li => li.id === req.params.id)
|
||||
if (!item || !item.media) return res.sendStatus(404)
|
||||
|
|
|
|||
|
|
@ -173,37 +173,6 @@ class PodcastController {
|
|||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
async openPodcastFeed(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user attempted to open podcast feed`, req.user.username)
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
|
||||
const feedData = this.rssFeedManager.openPodcastFeed(req.user, req.libraryItem, req.body)
|
||||
if (feedData.error) {
|
||||
return res.json({
|
||||
success: false,
|
||||
error: feedData.error
|
||||
})
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
feedUrl: feedData.feedUrl
|
||||
})
|
||||
}
|
||||
|
||||
async closePodcastFeed(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[PodcastController] Non-admin user attempted to close podcast feed`, req.user.username)
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
|
||||
this.rssFeedManager.closePodcastFeedForItem(req.params.id)
|
||||
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
async updateEpisode(req, res) {
|
||||
var libraryItem = req.libraryItem
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const Path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const date = require('date-and-time')
|
||||
const { Podcast } = require('podcast')
|
||||
const { getId } = require('../utils/index')
|
||||
const Logger = require('../Logger')
|
||||
|
||||
// Not functional at the moment
|
||||
|
|
@ -60,36 +60,72 @@ class RssFeedManager {
|
|||
}
|
||||
|
||||
openFeed(userId, slug, libraryItem, serverAddress) {
|
||||
const podcast = libraryItem.media
|
||||
const media = libraryItem.media
|
||||
const mediaMetadata = media.metadata
|
||||
const isPodcast = libraryItem.mediaType === 'podcast'
|
||||
|
||||
const feedUrl = `${serverAddress}/feed/${slug}`
|
||||
// Removed Podcast npm package and ip package
|
||||
const author = isPodcast ? mediaMetadata.author : mediaMetadata.authorName
|
||||
|
||||
const feed = new Podcast({
|
||||
title: podcast.metadata.title,
|
||||
description: podcast.metadata.description,
|
||||
title: mediaMetadata.title,
|
||||
description: mediaMetadata.description,
|
||||
feedUrl,
|
||||
siteUrl: serverAddress,
|
||||
imageUrl: podcast.coverPath ? `${serverAddress}/feed/${slug}/cover` : `${serverAddress}/Logo.png`,
|
||||
author: podcast.metadata.author || 'advplyr',
|
||||
siteUrl: `${serverAddress}/items/${libraryItem.id}`,
|
||||
imageUrl: media.coverPath ? `${serverAddress}/feed/${slug}/cover` : `${serverAddress}/Logo.png`,
|
||||
author: author || 'advplyr',
|
||||
language: 'en'
|
||||
})
|
||||
podcast.episodes.forEach((episode) => {
|
||||
var contentUrl = episode.audioTrack.contentUrl.replace(/\\/g, '/')
|
||||
contentUrl = contentUrl.replace(`/s/item/${libraryItem.id}`, `/feed/${slug}/item`)
|
||||
|
||||
feed.addItem({
|
||||
title: episode.title,
|
||||
description: episode.description || '',
|
||||
enclosure: {
|
||||
if (isPodcast) { // PODCAST EPISODES
|
||||
media.episodes.forEach((episode) => {
|
||||
var contentUrl = episode.audioTrack.contentUrl.replace(/\\/g, '/')
|
||||
contentUrl = contentUrl.replace(`/s/item/${libraryItem.id}`, `/feed/${slug}/item`)
|
||||
|
||||
feed.addItem({
|
||||
title: episode.title,
|
||||
description: episode.description || '',
|
||||
enclosure: {
|
||||
url: `${serverAddress}${contentUrl}`,
|
||||
type: episode.audioTrack.mimeType,
|
||||
size: episode.size
|
||||
},
|
||||
date: episode.pubDate || '',
|
||||
url: `${serverAddress}${contentUrl}`,
|
||||
type: episode.audioTrack.mimeType,
|
||||
size: episode.size
|
||||
},
|
||||
date: episode.pubDate || '',
|
||||
url: `${serverAddress}${contentUrl}`,
|
||||
author: podcast.metadata.author || 'advplyr'
|
||||
author: author || 'advplyr'
|
||||
})
|
||||
})
|
||||
})
|
||||
} else { // AUDIOBOOK EPISODES
|
||||
|
||||
// Example: <pubDate>Fri, 04 Feb 2015 00:00:00 GMT</pubDate>
|
||||
const audiobookPubDate = date.format(new Date(libraryItem.addedAt), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
|
||||
|
||||
media.tracks.forEach((audioTrack) => {
|
||||
var contentUrl = audioTrack.contentUrl.replace(/\\/g, '/')
|
||||
contentUrl = contentUrl.replace(`/s/item/${libraryItem.id}`, `/feed/${slug}/item`)
|
||||
|
||||
var title = audioTrack.title
|
||||
if (media.chapters.length) {
|
||||
// If audio track start and chapter start are within 1 seconds of eachother then use the chapter title
|
||||
var matchingChapter = media.chapters.find(ch => Math.abs(ch.start - audioTrack.startOffset) < 1)
|
||||
if (matchingChapter && matchingChapter.title) title = matchingChapter.title
|
||||
}
|
||||
|
||||
feed.addItem({
|
||||
title,
|
||||
description: '',
|
||||
enclosure: {
|
||||
url: `${serverAddress}${contentUrl}`,
|
||||
type: audioTrack.mimeType,
|
||||
size: audioTrack.metadata.size
|
||||
},
|
||||
date: audiobookPubDate,
|
||||
url: `${serverAddress}${contentUrl}`,
|
||||
author: author || 'advplyr'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const feedData = {
|
||||
id: slug,
|
||||
|
|
@ -97,7 +133,7 @@ class RssFeedManager {
|
|||
userId,
|
||||
libraryItemId: libraryItem.id,
|
||||
libraryItemPath: libraryItem.path,
|
||||
mediaCoverPath: podcast.coverPath,
|
||||
mediaCoverPath: media.coverPath,
|
||||
serverAddress: serverAddress,
|
||||
feedUrl,
|
||||
feed
|
||||
|
|
@ -106,7 +142,7 @@ class RssFeedManager {
|
|||
return feedData
|
||||
}
|
||||
|
||||
openPodcastFeed(user, libraryItem, options) {
|
||||
openFeedForItem(user, libraryItem, options) {
|
||||
const serverAddress = options.serverAddress
|
||||
const slug = options.slug
|
||||
|
||||
|
|
@ -118,12 +154,12 @@ class RssFeedManager {
|
|||
}
|
||||
|
||||
const feedData = this.openFeed(user.id, slug, libraryItem, serverAddress)
|
||||
Logger.debug(`[RssFeedManager] Opened podcast feed ${feedData.feedUrl}`)
|
||||
Logger.debug(`[RssFeedManager] Opened RSS feed ${feedData.feedUrl}`)
|
||||
this.emitter('rss_feed_open', { libraryItemId: libraryItem.id, feedUrl: feedData.feedUrl })
|
||||
return feedData
|
||||
}
|
||||
|
||||
closePodcastFeedForItem(libraryItemId) {
|
||||
closeFeedForItem(libraryItemId) {
|
||||
var feed = this.findFeedForItem(libraryItemId)
|
||||
if (!feed) return
|
||||
this.closeRssFeed(feed.id)
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@ class ApiRouter {
|
|||
this.router.get('/items/:id/scan', LibraryItemController.middleware.bind(this), LibraryItemController.scan.bind(this))
|
||||
this.router.get('/items/:id/audio-metadata', LibraryItemController.middleware.bind(this), LibraryItemController.updateAudioFileMetadata.bind(this))
|
||||
this.router.post('/items/:id/chapters', LibraryItemController.middleware.bind(this), LibraryItemController.updateMediaChapters.bind(this))
|
||||
this.router.post('/items/:id/open-feed', LibraryItemController.middleware.bind(this), LibraryItemController.openRSSFeed.bind(this))
|
||||
this.router.post('/items/:id/close-feed', LibraryItemController.middleware.bind(this), LibraryItemController.closeRSSFeed.bind(this))
|
||||
|
||||
this.router.post('/items/batch/delete', LibraryItemController.batchDelete.bind(this))
|
||||
this.router.post('/items/batch/update', LibraryItemController.batchUpdate.bind(this))
|
||||
|
|
@ -186,8 +188,6 @@ class ApiRouter {
|
|||
this.router.get('/podcasts/:id/downloads', PodcastController.middleware.bind(this), PodcastController.getEpisodeDownloads.bind(this))
|
||||
this.router.get('/podcasts/:id/clear-queue', PodcastController.middleware.bind(this), PodcastController.clearEpisodeDownloadQueue.bind(this))
|
||||
this.router.post('/podcasts/:id/download-episodes', PodcastController.middleware.bind(this), PodcastController.downloadEpisodes.bind(this))
|
||||
this.router.post('/podcasts/:id/open-feed', PodcastController.middleware.bind(this), PodcastController.openPodcastFeed.bind(this))
|
||||
this.router.post('/podcasts/:id/close-feed', PodcastController.middleware.bind(this), PodcastController.closePodcastFeed.bind(this))
|
||||
this.router.patch('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.updateEpisode.bind(this))
|
||||
|
||||
//
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@ module.exports = {
|
|||
books: [libraryItemJson],
|
||||
inProgress: bookInProgress,
|
||||
bookInProgressLastUpdate: bookInProgress ? mediaProgress.lastUpdate : null,
|
||||
sequenceInProgress: bookInProgress ? libraryItemJson.seriesSequence : null
|
||||
firstBookUnread: bookInProgress ? libraryItemJson : null
|
||||
}
|
||||
seriesMap[librarySeries.id] = series
|
||||
|
||||
|
|
@ -445,10 +445,18 @@ module.exports = {
|
|||
|
||||
if (bookInProgress) { // Update if this series is in progress
|
||||
seriesMap[librarySeries.id].inProgress = true
|
||||
if (!seriesMap[librarySeries.id].sequenceInProgress || (librarySeries.sequence && String(librarySeries.sequence).localeCompare(String(seriesMap[librarySeries.id].sequenceInProgress), undefined, { sensitivity: 'base', numeric: true }) > 0)) {
|
||||
seriesMap[librarySeries.id].sequenceInProgress = librarySeries.sequence
|
||||
|
||||
if (seriesMap[librarySeries.id].bookInProgressLastUpdate > mediaProgress.lastUpdate) {
|
||||
seriesMap[librarySeries.id].bookInProgressLastUpdate = mediaProgress.lastUpdate
|
||||
}
|
||||
} else if (!seriesMap[librarySeries.id].firstBookUnread) {
|
||||
seriesMap[librarySeries.id].firstBookUnread = libraryItemJson
|
||||
} else if (libraryItemJson.seriesSequence) {
|
||||
// If current firstBookUnread has a series sequence greater than this series sequence, then update firstBookUnread
|
||||
const firstBookUnreadSequence = seriesMap[librarySeries.id].firstBookUnread.seriesSequence
|
||||
if (!firstBookUnreadSequence || String(firstBookUnreadSequence).localeCompare(String(librarySeries.sequence), undefined, { sensitivity: 'base', numeric: true }) > 0) {
|
||||
seriesMap[librarySeries.id].firstBookUnread = libraryItemJson
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -545,11 +553,8 @@ module.exports = {
|
|||
if (seriesMap[seriesId].inProgress) {
|
||||
seriesMap[seriesId].books = naturalSort(seriesMap[seriesId].books).asc(li => li.seriesSequence)
|
||||
|
||||
const nextBookInSeries = seriesMap[seriesId].books.find(li => {
|
||||
if (!seriesMap[seriesId].sequenceInProgress) return true
|
||||
// True if book series sequence is greater than the current book sequence in progress
|
||||
return String(li.seriesSequence).localeCompare(String(seriesMap[seriesId].sequenceInProgress), undefined, { sensitivity: 'base', numeric: true }) > 0
|
||||
})
|
||||
// NEW implementation takes the first book unread with the smallest series sequence
|
||||
const nextBookInSeries = seriesMap[seriesId].firstBookUnread
|
||||
|
||||
if (nextBookInSeries) {
|
||||
const bookForContinueSeries = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue