mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-09 21:39:37 +00:00
Merge branch 'master' into faster-scan-for-empty-series
This commit is contained in:
commit
3ad4f05449
20 changed files with 120 additions and 143 deletions
|
|
@ -184,6 +184,7 @@ class Server {
|
|||
'/library/:library/series/:id?',
|
||||
'/library/:library/podcast/search',
|
||||
'/library/:library/podcast/latest',
|
||||
'/library/:library/podcast/download-queue',
|
||||
'/config/users/:id',
|
||||
'/config/users/:id/sessions',
|
||||
'/config/item-metadata-utils/:id',
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ class PlaylistController {
|
|||
|
||||
await Database.createPlaylistMediaItem(playlistMediaItem)
|
||||
const jsonExpanded = await req.playlist.getOldJsonExpanded()
|
||||
SocketAuthority.clientEmitter(playlist.userId, 'playlist_updated', jsonExpanded)
|
||||
SocketAuthority.clientEmitter(jsonExpanded.userId, 'playlist_updated', jsonExpanded)
|
||||
res.json(jsonExpanded)
|
||||
}
|
||||
|
||||
|
|
@ -376,9 +376,9 @@ class PlaylistController {
|
|||
if (!numMediaItems) {
|
||||
Logger.info(`[PlaylistController] Playlist "${req.playlist.name}" has no more items - removing it`)
|
||||
await req.playlist.destroy()
|
||||
SocketAuthority.clientEmitter(playlist.userId, 'playlist_removed', jsonExpanded)
|
||||
SocketAuthority.clientEmitter(jsonExpanded.userId, 'playlist_removed', jsonExpanded)
|
||||
} else {
|
||||
SocketAuthority.clientEmitter(playlist.userId, 'playlist_updated', jsonExpanded)
|
||||
SocketAuthority.clientEmitter(jsonExpanded.userId, 'playlist_updated', jsonExpanded)
|
||||
}
|
||||
}
|
||||
res.json(jsonExpanded)
|
||||
|
|
|
|||
|
|
@ -229,38 +229,6 @@ class CoverManager {
|
|||
}
|
||||
}
|
||||
|
||||
async saveEmbeddedCoverArt(libraryItem) {
|
||||
let audioFileWithCover = null
|
||||
if (libraryItem.mediaType === 'book') {
|
||||
audioFileWithCover = libraryItem.media.audioFiles.find(af => af.embeddedCoverArt)
|
||||
} else if (libraryItem.mediaType == 'podcast') {
|
||||
const episodeWithCover = libraryItem.media.episodes.find(ep => ep.audioFile.embeddedCoverArt)
|
||||
if (episodeWithCover) audioFileWithCover = episodeWithCover.audioFile
|
||||
} else if (libraryItem.mediaType === 'music') {
|
||||
audioFileWithCover = libraryItem.media.audioFile
|
||||
}
|
||||
if (!audioFileWithCover) return false
|
||||
|
||||
const coverDirPath = this.getCoverDirectory(libraryItem)
|
||||
await fs.ensureDir(coverDirPath)
|
||||
|
||||
const coverFilename = audioFileWithCover.embeddedCoverArt === 'png' ? 'cover.png' : 'cover.jpg'
|
||||
const coverFilePath = Path.join(coverDirPath, coverFilename)
|
||||
|
||||
const coverAlreadyExists = await fs.pathExists(coverFilePath)
|
||||
if (coverAlreadyExists) {
|
||||
Logger.warn(`[CoverManager] Extract embedded cover art but cover already exists for "${libraryItem.media.metadata.title}" - bail`)
|
||||
return false
|
||||
}
|
||||
|
||||
const success = await extractCoverArt(audioFileWithCover.metadata.path, coverFilePath)
|
||||
if (success) {
|
||||
libraryItem.updateMediaCover(coverFilePath)
|
||||
return coverFilePath
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract cover art from audio file and save for library item
|
||||
* @param {import('../models/Book').AudioFileObject[]} audioFiles
|
||||
|
|
@ -268,7 +236,7 @@ class CoverManager {
|
|||
* @param {string} [libraryItemPath] null for isFile library items
|
||||
* @returns {Promise<string>} returns cover path
|
||||
*/
|
||||
async saveEmbeddedCoverArtNew(audioFiles, libraryItemId, libraryItemPath) {
|
||||
async saveEmbeddedCoverArt(audioFiles, libraryItemId, libraryItemPath) {
|
||||
let audioFileWithCover = audioFiles.find(af => af.embeddedCoverArt)
|
||||
if (!audioFileWithCover) return null
|
||||
|
||||
|
|
@ -291,6 +259,7 @@ class CoverManager {
|
|||
|
||||
const success = await extractCoverArt(audioFileWithCover.metadata.path, coverFilePath)
|
||||
if (success) {
|
||||
await CacheManager.purgeCoverCache(libraryItemId)
|
||||
return coverFilePath
|
||||
}
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const { DataTypes, Model, literal } = require('sequelize')
|
||||
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
||||
|
||||
const oldAuthor = require('../objects/entities/Author')
|
||||
|
||||
|
|
@ -114,14 +114,11 @@ class Author extends Model {
|
|||
static async getOldByNameAndLibrary(authorName, libraryId) {
|
||||
const author = (await this.findOne({
|
||||
where: [
|
||||
literal(`name = ':authorName' COLLATE NOCASE`),
|
||||
where(fn('lower', col('name')), authorName.toLowerCase()),
|
||||
{
|
||||
libraryId
|
||||
}
|
||||
],
|
||||
replacements: {
|
||||
authorName
|
||||
}
|
||||
]
|
||||
}))?.getOldAuthor()
|
||||
return author
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const { DataTypes, Model, literal } = require('sequelize')
|
||||
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
||||
|
||||
const oldSeries = require('../objects/entities/Series')
|
||||
|
||||
|
|
@ -105,14 +105,11 @@ class Series extends Model {
|
|||
static async getOldByNameAndLibrary(seriesName, libraryId) {
|
||||
const series = (await this.findOne({
|
||||
where: [
|
||||
literal(`name = ':seriesName' COLLATE NOCASE`),
|
||||
where(fn('lower', col('name')), seriesName.toLowerCase()),
|
||||
{
|
||||
libraryId
|
||||
}
|
||||
],
|
||||
replacements: {
|
||||
seriesName
|
||||
}
|
||||
]
|
||||
}))?.getOldSeries()
|
||||
return series
|
||||
}
|
||||
|
|
|
|||
|
|
@ -553,13 +553,17 @@ class ApiRouter {
|
|||
continue
|
||||
}
|
||||
|
||||
if (mediaMetadata.authors[i].id?.startsWith('new')) {
|
||||
mediaMetadata.authors[i].id = null
|
||||
}
|
||||
|
||||
// Ensure the ID for the author exists
|
||||
if (mediaMetadata.authors[i].id && !(await Database.checkAuthorExists(libraryId, mediaMetadata.authors[i].id))) {
|
||||
Logger.warn(`[ApiRouter] Author id "${mediaMetadata.authors[i].id}" does not exist`)
|
||||
mediaMetadata.authors[i].id = null
|
||||
}
|
||||
|
||||
if (!mediaMetadata.authors[i].id || mediaMetadata.authors[i].id.startsWith('new')) {
|
||||
if (!mediaMetadata.authors[i].id) {
|
||||
let author = await Database.authorModel.getOldByNameAndLibrary(authorName, libraryId)
|
||||
if (!author) {
|
||||
author = new Author()
|
||||
|
|
@ -590,13 +594,17 @@ class ApiRouter {
|
|||
continue
|
||||
}
|
||||
|
||||
if (mediaMetadata.series[i].id?.startsWith('new')) {
|
||||
mediaMetadata.series[i].id = null
|
||||
}
|
||||
|
||||
// Ensure the ID for the series exists
|
||||
if (mediaMetadata.series[i].id && !(await Database.checkSeriesExists(libraryId, mediaMetadata.series[i].id))) {
|
||||
Logger.warn(`[ApiRouter] Series id "${mediaMetadata.series[i].id}" does not exist`)
|
||||
mediaMetadata.series[i].id = null
|
||||
}
|
||||
|
||||
if (!mediaMetadata.series[i].id || mediaMetadata.series[i].id.startsWith('new')) {
|
||||
if (!mediaMetadata.series[i].id) {
|
||||
let seriesItem = await Database.seriesModel.getOldByNameAndLibrary(seriesName, libraryId)
|
||||
if (!seriesItem) {
|
||||
seriesItem = new Series()
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ class BookScanner {
|
|||
// If no cover then extract cover from audio file if available OR search for cover if enabled in server settings
|
||||
if (!media.coverPath) {
|
||||
const libraryItemDir = existingLibraryItem.isFile ? null : existingLibraryItem.path
|
||||
const extractedCoverPath = await CoverManager.saveEmbeddedCoverArtNew(media.audioFiles, existingLibraryItem.id, libraryItemDir)
|
||||
const extractedCoverPath = await CoverManager.saveEmbeddedCoverArt(media.audioFiles, existingLibraryItem.id, libraryItemDir)
|
||||
if (extractedCoverPath) {
|
||||
libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" extracted embedded cover art from audio file to path "${extractedCoverPath}"`)
|
||||
media.coverPath = extractedCoverPath
|
||||
|
|
@ -461,7 +461,7 @@ class BookScanner {
|
|||
if (!bookObject.coverPath) {
|
||||
const libraryItemDir = libraryItemObj.isFile ? null : libraryItemObj.path
|
||||
// Extract and save embedded cover art
|
||||
const extractedCoverPath = await CoverManager.saveEmbeddedCoverArtNew(scannedAudioFiles, libraryItemObj.id, libraryItemDir)
|
||||
const extractedCoverPath = await CoverManager.saveEmbeddedCoverArt(scannedAudioFiles, libraryItemObj.id, libraryItemDir)
|
||||
if (extractedCoverPath) {
|
||||
bookObject.coverPath = extractedCoverPath
|
||||
} else if (Database.serverSettings.scannerFindCovers) {
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ class PodcastScanner {
|
|||
// If no cover then extract cover from audio file if available
|
||||
if (!media.coverPath && existingPodcastEpisodes.length) {
|
||||
const audioFiles = existingPodcastEpisodes.map(ep => ep.audioFile)
|
||||
const extractedCoverPath = await CoverManager.saveEmbeddedCoverArtNew(audioFiles, existingLibraryItem.id, existingLibraryItem.path)
|
||||
const extractedCoverPath = await CoverManager.saveEmbeddedCoverArt(audioFiles, existingLibraryItem.id, existingLibraryItem.path)
|
||||
if (extractedCoverPath) {
|
||||
libraryScan.addLog(LogLevel.DEBUG, `Updating podcast "${podcastMetadata.title}" extracted embedded cover art from audio file to path "${extractedCoverPath}"`)
|
||||
media.coverPath = extractedCoverPath
|
||||
|
|
@ -279,7 +279,7 @@ class PodcastScanner {
|
|||
// If cover was not found in folder then check embedded covers in audio files
|
||||
if (!podcastObject.coverPath && scannedAudioFiles.length) {
|
||||
// Extract and save embedded cover art
|
||||
podcastObject.coverPath = await CoverManager.saveEmbeddedCoverArtNew(scannedAudioFiles, libraryItemObj.id, libraryItemObj.path)
|
||||
podcastObject.coverPath = await CoverManager.saveEmbeddedCoverArt(scannedAudioFiles, libraryItemObj.id, libraryItemObj.path)
|
||||
}
|
||||
|
||||
libraryItemObj.podcast = podcastObject
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ module.exports = (nameToParse, partToReturn, fixCase, stopOnError, useLongLists)
|
|||
namePartWords[j].slice(3).toLowerCase();
|
||||
} else if (
|
||||
namePartLabels[j] === 'suffix' &&
|
||||
nameParts[j].slice(-1) !== '.' &&
|
||||
!suffixList.indexOf(nameParts[j].toLowerCase())
|
||||
namePartWords[j].slice(-1) !== '.' &&
|
||||
!suffixList.indexOf(namePartWords[j].toLowerCase())
|
||||
) { // Convert suffix abbreviations to UPPER CASE
|
||||
if (namePartWords[j] === namePartWords[j].toLowerCase()) {
|
||||
namePartWords[j] = namePartWords[j].toUpperCase();
|
||||
|
|
@ -343,4 +343,4 @@ module.exports = (nameToParse, partToReturn, fixCase, stopOnError, useLongLists)
|
|||
|
||||
parsedName = fixParsedNameCase(parsedName, fixCase);
|
||||
return partToReturn === 'all' ? parsedName : parsedName[partToReturn];
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -469,12 +469,20 @@ module.exports = {
|
|||
* @returns {Promise<{ totalSize:number, totalDuration:number, numAudioFiles:number, totalItems:number}>}
|
||||
*/
|
||||
async getPodcastLibraryStats(libraryId) {
|
||||
const [statResults] = await Database.sequelize.query(`SELECT SUM(json_extract(pe.audioFile, '$.duration')) AS totalDuration, SUM(li.size) AS totalSize, COUNT(DISTINCT(li.id)) AS totalItems, COUNT(pe.id) AS numAudioFiles FROM libraryItems li, podcasts p LEFT OUTER JOIN podcastEpisodes pe ON pe.podcastId = p.id WHERE p.id = li.mediaId AND li.libraryId = :libraryId;`, {
|
||||
const [sizeResults] = await Database.sequelize.query(`SELECT SUM(li.size) AS totalSize FROM libraryItems li WHERE li.mediaType = "podcast" AND li.libraryId = :libraryId;`, {
|
||||
replacements: {
|
||||
libraryId
|
||||
}
|
||||
})
|
||||
return statResults[0]
|
||||
const [statResults] = await Database.sequelize.query(`SELECT SUM(json_extract(pe.audioFile, '$.duration')) AS totalDuration, COUNT(DISTINCT(li.id)) AS totalItems, COUNT(pe.id) AS numAudioFiles FROM libraryItems li, podcasts p LEFT OUTER JOIN podcastEpisodes pe ON pe.podcastId = p.id WHERE p.id = li.mediaId AND li.libraryId = :libraryId;`, {
|
||||
replacements: {
|
||||
libraryId
|
||||
}
|
||||
})
|
||||
return {
|
||||
...statResults[0],
|
||||
totalSize: sizeResults[0].totalSize || 0
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue