mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-05-22 11:21:31 +00:00
Improve chapter generation code and extract it into its own function
This commit is contained in:
parent
bb7fcc1420
commit
9d4a2a8a59
1 changed files with 81 additions and 69 deletions
|
|
@ -87,76 +87,12 @@ class PodcastEpisode extends Model {
|
||||||
} else if (rssPodcastEpisode.chapters?.length) {
|
} else if (rssPodcastEpisode.chapters?.length) {
|
||||||
podcastEpisode.chapters = rssPodcastEpisode.chapters.map((ch) => ({ ...ch }))
|
podcastEpisode.chapters = rssPodcastEpisode.chapters.map((ch) => ({ ...ch }))
|
||||||
} else {
|
} else {
|
||||||
const timeMarkerRegex = /\b(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\b/
|
|
||||||
const chapterTitleRegex = /\b\d{1,2}:\d{1,2}(?::\d{1,2})?\b(.+)$/
|
|
||||||
|
|
||||||
Logger.debug("Podcast episode doesn't have chapters, attempting to generate them from timestamps", rssPodcastEpisode.title)
|
Logger.debug("Podcast episode doesn't have chapters, attempting to generate them from timestamps", rssPodcastEpisode.title)
|
||||||
|
try {
|
||||||
var errorMessage = null
|
let autoGeneratedChapters = PodcastEpisode.autoGenerateChaptersFromTimestamps(podcastEpisode.description, podcastEpisode.audioFile.duration)
|
||||||
var descriptionLines = podcastEpisode.description.split('</p>')
|
podcastEpisode.chapters = autoGeneratedChapters
|
||||||
var chaptersToPush = []
|
} catch (error) {
|
||||||
|
Logger.error(`[PodcastEpisode] createFromRssPodcastEpisode: Failed to auto generate chapters for "${podcastEpisode.title}"`, error)
|
||||||
for (let i = 0; i < descriptionLines.length; i++) {
|
|
||||||
let line = descriptionLines[i]
|
|
||||||
Logger.debug('Description Line:', line)
|
|
||||||
|
|
||||||
let match = timeMarkerRegex.exec(line)
|
|
||||||
if (match == null) continue
|
|
||||||
|
|
||||||
Logger.debug('Matches:', match)
|
|
||||||
|
|
||||||
let first = match[1]
|
|
||||||
let second = match[2]
|
|
||||||
let third = match[3]
|
|
||||||
|
|
||||||
let hours = 0
|
|
||||||
let minutes = 0
|
|
||||||
let seconds = 0
|
|
||||||
|
|
||||||
// If there's three components then we can assume its hh:mm:ss
|
|
||||||
if (first && second && third) {
|
|
||||||
hours = Number(first)
|
|
||||||
minutes = Number(second)
|
|
||||||
seconds = Number(third)
|
|
||||||
} else if (first && second) // otherwise assume mm:ss
|
|
||||||
{
|
|
||||||
minutes = Number(first)
|
|
||||||
seconds = Number(second)
|
|
||||||
} else {
|
|
||||||
// Unknown timestamp state
|
|
||||||
errorMessage = `Unknown timestamp format in description, line ${line}`
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
let startTime = seconds + minutes * 60 + hours * 60 * 60
|
|
||||||
let chapterTitleMatch = chapterTitleRegex.exec(line)
|
|
||||||
Logger.debug('Chapter Title Matches:', chapterTitleMatch)
|
|
||||||
|
|
||||||
if (chapterTitleMatch == null && chapterTitleMatch.length >= 2) {
|
|
||||||
// Unknown chapter state
|
|
||||||
errorMessage = `Unable to get chapter title from description, line ${line}`
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
let chapter = { title: chapterTitleMatch[1].trim(), id: i, start: startTime }
|
|
||||||
|
|
||||||
if (chaptersToPush.length > 0) {
|
|
||||||
chaptersToPush[chaptersToPush.length - 1].end = startTime
|
|
||||||
}
|
|
||||||
|
|
||||||
chaptersToPush.push(chapter)
|
|
||||||
|
|
||||||
Logger.debug('Added chapter', chapter)
|
|
||||||
}
|
|
||||||
if (errorMessage == null) {
|
|
||||||
if (chaptersToPush.length > 0) {
|
|
||||||
chaptersToPush[chaptersToPush.length - 1].end = podcastEpisode.audioFile.duration
|
|
||||||
}
|
|
||||||
|
|
||||||
podcastEpisode.chapters.push(...chaptersToPush)
|
|
||||||
Logger.debug(`Successfully gnerated ${podcastEpisode.chapters.length} chapters`)
|
|
||||||
} else {
|
|
||||||
Logger.error(`Unable generate chapters from podcast description, error '${errorMessage}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -309,6 +245,82 @@ class PodcastEpisode extends Model {
|
||||||
|
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} podcastDescription
|
||||||
|
* @param {number} audioDurationSecs
|
||||||
|
* @returns {ChapterObject[]}
|
||||||
|
*/
|
||||||
|
static autoGenerateChaptersFromTimestamps(podcastDescription, audioDurationSecs) {
|
||||||
|
if (podcastDescription == null) {
|
||||||
|
throw new Error('Description must not be null')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioDurationSecs == null) {
|
||||||
|
throw new Error('Audio duration must not be null')
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestampRegex = /\b(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\b/
|
||||||
|
const chapterTitleRegex = /\b\d{1,2}:\d{1,2}(?::\d{1,2})?\b(?:\s+|\))(.+)$/
|
||||||
|
const descriptionLineSplitRegex = /\<\s*\/\s*p\s*\>|\<\s*br\s*\s*\/\>|\n/
|
||||||
|
|
||||||
|
var descriptionLines = podcastDescription.split(descriptionLineSplitRegex)
|
||||||
|
var newChapters = []
|
||||||
|
|
||||||
|
for (let i = 0; i < descriptionLines.length; i++) {
|
||||||
|
let line = descriptionLines[i]
|
||||||
|
|
||||||
|
let match = timestampRegex.exec(line)
|
||||||
|
if (match == null) continue
|
||||||
|
|
||||||
|
let first = match[1]
|
||||||
|
let second = match[2]
|
||||||
|
let third = match[3]
|
||||||
|
|
||||||
|
let hours = 0
|
||||||
|
let minutes = 0
|
||||||
|
let seconds = 0
|
||||||
|
|
||||||
|
// If there's three components then we can assume its hh:mm:ss
|
||||||
|
if (first && second && third) {
|
||||||
|
hours = Number(first)
|
||||||
|
minutes = Number(second)
|
||||||
|
seconds = Number(third)
|
||||||
|
} else if (first && second) // otherwise assume mm:ss
|
||||||
|
{
|
||||||
|
minutes = Number(first)
|
||||||
|
seconds = Number(second)
|
||||||
|
}
|
||||||
|
|
||||||
|
let startTime = seconds + minutes * 60 + hours * 60 * 60
|
||||||
|
let chapterTitleMatch = chapterTitleRegex.exec(line)
|
||||||
|
|
||||||
|
if (chapterTitleMatch == null || chapterTitleMatch.length < 2) {
|
||||||
|
// Unknown chapter state
|
||||||
|
throw new Error(`Unable to get chapter title from description, line ${line}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let chapter = { title: chapterTitleMatch[1].trim(), id: newChapters.length + 1, start: startTime }
|
||||||
|
|
||||||
|
if (newChapters.length > 0) {
|
||||||
|
newChapters[newChapters.length - 1].end = startTime
|
||||||
|
}
|
||||||
|
|
||||||
|
newChapters.push(chapter)
|
||||||
|
}
|
||||||
|
if (newChapters.length > 0) {
|
||||||
|
newChapters[newChapters.length - 1].end = audioDurationSecs
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.debug(`Successfully gnerated ${newChapters.length} chapters`)
|
||||||
|
|
||||||
|
if (newChapters.length == 1) {
|
||||||
|
throw new Error('Only one chapter found, treating as invalid description')
|
||||||
|
}
|
||||||
|
|
||||||
|
return newChapters
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PodcastEpisode
|
module.exports = PodcastEpisode
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue