Merge branch 'master' into faster-scan-for-empty-series

This commit is contained in:
advplyr 2023-09-17 15:47:06 -05:00
commit 3ad4f05449
20 changed files with 120 additions and 143 deletions

View file

@ -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',

View file

@ -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)

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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()

View file

@ -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) {

View file

@ -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

View file

@ -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];
};
};

View file

@ -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
}
},
/**