diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0213d517..4f4f24a7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,40 +1,32 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node { - "name": "Audiobookshelf", - "build": { - "dockerfile": "Dockerfile", - // Update 'VARIANT' to pick a Node version: 18, 16, 14. - // Append -bullseye or -buster to pin to an OS version. - // Use -bullseye variants on local arm64/Apple Silicon. - "args": { - "VARIANT": "20" - } - }, - "mounts": [ - "source=abs-server-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume", - "source=abs-client-node_modules,target=${containerWorkspaceFolder}/client/node_modules,type=volume" - ], - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [ - 3000, - 3333 - ], - // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "sh .devcontainer/post-create.sh", - // Configure tool-specific properties. - "customizations": { - // Configure properties specific to VS Code. - "vscode": { - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "dbaeumer.vscode-eslint", - "octref.vetur" - ] - } - } - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} \ No newline at end of file + "name": "Audiobookshelf", + "build": { + "dockerfile": "Dockerfile", + // Update 'VARIANT' to pick a Node version: 18, 16, 14. + // Append -bullseye or -buster to pin to an OS version. + // Use -bullseye variants on local arm64/Apple Silicon. + "args": { + "VARIANT": "20" + } + }, + "mounts": ["source=abs-server-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume", "source=abs-client-node_modules,target=${containerWorkspaceFolder}/client/node_modules,type=volume", "source=/home/harry/Music/ABS-Dev,target=/podcasts,type=bind,consistency=cached"], + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [3000, 3333], + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "sh .devcontainer/post-create.sh", + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Add the IDs of extensions you want installed when the container is created. + "extensions": ["dbaeumer.vscode-eslint", "octref.vetur"] + } + }, + "runArgs": ["-p=3333:3333"] + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/server/models/PodcastEpisode.js b/server/models/PodcastEpisode.js index defd4748..f87cc76b 100644 --- a/server/models/PodcastEpisode.js +++ b/server/models/PodcastEpisode.js @@ -1,6 +1,7 @@ const { DataTypes, Model } = require('sequelize') const libraryItemsPodcastFilters = require('../utils/queries/libraryItemsPodcastFilters') const Logger = require('../Logger') +const { logger } = require('sequelize/lib/utils/logger') /** * @typedef ChapterObject * @property {number} id @@ -87,11 +88,15 @@ class PodcastEpisode extends Model { } else if (rssPodcastEpisode.chapters?.length) { podcastEpisode.chapters = rssPodcastEpisode.chapters.map((ch) => ({ ...ch })) } else { - const timeMarkerRegex = /\b(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\b/m + 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 didn't have chapters", rssPodcastEpisode.title) + Logger.debug("Podcast episode doesn't have chapters, attempting to generate them from timestamps", rssPodcastEpisode.title) + var errorMessage = null var descriptionLines = podcastEpisode.description.split('

') + var chaptersToPush = [] + for (let i = 0; i < descriptionLines.length; i++) { let line = descriptionLines[i] Logger.debug('Description Line:', line) @@ -99,7 +104,7 @@ class PodcastEpisode extends Model { let match = timeMarkerRegex.exec(line) if (match == null) continue - Logger.debug('matches:', match) + Logger.debug('Matches:', match) let first = match[1] let second = match[2] @@ -118,23 +123,42 @@ class PodcastEpisode extends Model { { 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 chapter = { title: `Chapter ${i}`, id: i, start: startTime } + 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 (podcastEpisode.chapters.length > 0) { podcastEpisode.chapters[podcastEpisode.chapters.length - 1].end = startTime } - podcastEpisode.chapters.push(chapter) + chaptersToPush.push(chapter) Logger.debug('Added chapter', chapter) } - if (podcastEpisode.chapters.length > 0) { - podcastEpisode.chapters[podcastEpisode.chapters.length - 1].end = podcastEpisode.audioFile.duration + if (errorMessage == null) { + if (podcastEpisode.chapters.length > 0) { + podcastEpisode.chapters[podcastEpisode.chapters.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}`) } - Logger.debug('Chapters', podcastEpisode.chapters) } return this.create(podcastEpisode)