mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-07 09:51:37 +00:00
fix: write per-episode metadata files for podcast libraries
The metadata.json for podcasts only contained podcast-level metadata (title, author, etc.) but no episode data. When a podcast library was moved or restored from backup, all episode metadata was lost. This writes per-episode .json files alongside each episode's audio file (e.g. episode.mp3 -> episode.json) containing title, subtitle, season, episode, episodeType, pubDate, publishedAt, description, and chapters. This matches the flat structure approach suggested in issue #5093 since podcast libraries are structured differently from books (flat vs directory-based). Fixes #5093
This commit is contained in:
parent
03542a540f
commit
49c06f16f5
1 changed files with 52 additions and 12 deletions
|
|
@ -443,18 +443,7 @@ class PodcastScanner {
|
||||||
asin: libraryItem.media.asin,
|
asin: libraryItem.media.asin,
|
||||||
language: libraryItem.media.language,
|
language: libraryItem.media.language,
|
||||||
explicit: !!libraryItem.media.explicit,
|
explicit: !!libraryItem.media.explicit,
|
||||||
podcastType: libraryItem.media.podcastType,
|
podcastType: libraryItem.media.podcastType
|
||||||
episodes: (libraryItem.media.podcastEpisodes || []).map((ep) => ({
|
|
||||||
title: ep.title,
|
|
||||||
subtitle: ep.subtitle,
|
|
||||||
season: ep.season,
|
|
||||||
episode: ep.episode,
|
|
||||||
episodeType: ep.episodeType,
|
|
||||||
pubDate: ep.pubDate,
|
|
||||||
publishedAt: ep.publishedAt,
|
|
||||||
description: ep.description,
|
|
||||||
chapters: ep.chapters || []
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
return fsExtra
|
return fsExtra
|
||||||
.writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2))
|
.writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2))
|
||||||
|
|
@ -488,6 +477,11 @@ class PodcastScanner {
|
||||||
|
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Success saving abmetadata to "${metadataFilePath}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Success saving abmetadata to "${metadataFilePath}"`)
|
||||||
|
|
||||||
|
// Write per-episode metadata files alongside each episode's audio file
|
||||||
|
if (storeMetadataWithItem && libraryItem.media.podcastEpisodes?.length) {
|
||||||
|
await this.saveEpisodeMetadataFiles(libraryItem, libraryItem.media.podcastEpisodes, libraryScan)
|
||||||
|
}
|
||||||
|
|
||||||
return metadataLibraryFile
|
return metadataLibraryFile
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|
@ -495,5 +489,51 @@ class PodcastScanner {
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write per-episode metadata files alongside each episode's audio file.
|
||||||
|
* For an episode at /path/to/episode.mp3, writes /path/to/episode.json
|
||||||
|
* containing title, description, pubDate, chapters, etc.
|
||||||
|
*
|
||||||
|
* @param {import('../models/LibraryItem')} libraryItem
|
||||||
|
* @param {import('../models/PodcastEpisode')[]} podcastEpisodes
|
||||||
|
* @param {import('./LibraryScan')} libraryScan
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async saveEpisodeMetadataFiles(libraryItem, podcastEpisodes, libraryScan) {
|
||||||
|
for (const episode of podcastEpisodes) {
|
||||||
|
if (!episode.audioFile?.metadata?.path) continue
|
||||||
|
|
||||||
|
const audioFilePath = episode.audioFile.metadata.path
|
||||||
|
const episodeMetadataPath = audioFilePath.replace(/\.[^.]+$/, '.json')
|
||||||
|
|
||||||
|
const episodeJson = {
|
||||||
|
title: episode.title,
|
||||||
|
subtitle: episode.subtitle,
|
||||||
|
season: episode.season,
|
||||||
|
episode: episode.episode,
|
||||||
|
episodeType: episode.episodeType,
|
||||||
|
pubDate: episode.pubDate,
|
||||||
|
publishedAt: episode.publishedAt,
|
||||||
|
description: episode.description,
|
||||||
|
chapters: episode.chapters || []
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fsExtra.writeFile(episodeMetadataPath, JSON.stringify(episodeJson, null, 2))
|
||||||
|
libraryScan.addLog(LogLevel.DEBUG, `Saved episode metadata to "${episodeMetadataPath}"`)
|
||||||
|
|
||||||
|
// Add to libraryFiles if not already tracked
|
||||||
|
const metadataLibraryFile = libraryItem.libraryFiles.find((lf) => lf.metadata.path === filePathToPOSIX(episodeMetadataPath))
|
||||||
|
if (!metadataLibraryFile) {
|
||||||
|
const newLibraryFile = new LibraryFile()
|
||||||
|
await newLibraryFile.setDataFromPath(episodeMetadataPath, Path.basename(episodeMetadataPath))
|
||||||
|
libraryItem.libraryFiles.push(newLibraryFile.toJSON())
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
libraryScan.addLog(LogLevel.ERROR, `Failed to save episode metadata at "${episodeMetadataPath}"`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports = new PodcastScanner()
|
module.exports = new PodcastScanner()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue