const Sequelize = require('sequelize') const Database = require('../../Database') const PlaybackSession = require('../../models/PlaybackSession') const fsExtra = require('../../libs/fsExtra') module.exports = { /** * * @param {number} year YYYY * @returns {Promise} */ async getListeningSessionsForYear(year) { const sessions = await Database.playbackSessionModel.findAll({ where: { createdAt: { [Sequelize.Op.gte]: `${year}-01-01`, [Sequelize.Op.lt]: `${year + 1}-01-01` } } }) return sessions }, /** * * @param {number} year YYYY * @returns {Promise} */ async getNumAuthorsAddedForYear(year) { const count = await Database.authorModel.count({ where: { createdAt: { [Sequelize.Op.gte]: `${year}-01-01`, [Sequelize.Op.lt]: `${year + 1}-01-01` } } }) return count }, /** * * @param {number} year YYYY * @returns {Promise} */ async getBooksAddedForYear(year) { const books = await Database.bookModel.findAll({ attributes: ['id', 'title', 'coverPath', 'duration', 'createdAt'], where: { createdAt: { [Sequelize.Op.gte]: `${year}-01-01`, [Sequelize.Op.lt]: `${year + 1}-01-01` } }, include: { model: Database.libraryItemModel, attributes: ['id', 'mediaId', 'mediaType', 'size'], required: true }, order: Database.sequelize.random() }) return books }, /** * * @param {number} year YYYY */ async getStatsForYear(year) { const booksAdded = await this.getBooksAddedForYear(year) let totalBooksAddedSize = 0 let totalBooksAddedDuration = 0 const booksWithCovers = [] for (const book of booksAdded) { // Grab first 25 that have a cover if (book.coverPath && !booksWithCovers.includes(book.libraryItem.id) && booksWithCovers.length < 25 && (await fsExtra.pathExists(book.coverPath))) { booksWithCovers.push(book.libraryItem.id) } if (book.duration && !isNaN(book.duration)) { totalBooksAddedDuration += book.duration } if (book.libraryItem.size && !isNaN(book.libraryItem.size)) { totalBooksAddedSize += book.libraryItem.size } } const numAuthorsAdded = await this.getNumAuthorsAddedForYear(year) let authorListeningMap = {} let narratorListeningMap = {} let genreListeningMap = {} const listeningSessions = await this.getListeningSessionsForYear(year) let totalListeningTime = 0 for (const ls of listeningSessions) { totalListeningTime += ls.timeListening || 0 const authors = ls.mediaMetadata?.authors || [] authors.forEach((au) => { if (!authorListeningMap[au.name]) authorListeningMap[au.name] = 0 authorListeningMap[au.name] += ls.timeListening || 0 }) const narrators = ls.mediaMetadata?.narrators || [] narrators.forEach((narrator) => { if (!narratorListeningMap[narrator]) narratorListeningMap[narrator] = 0 narratorListeningMap[narrator] += ls.timeListening || 0 }) // Filter out bad genres like "audiobook" and "audio book" const genres = (ls.mediaMetadata?.genres || []).filter((g) => g && !g.toLowerCase().includes('audiobook') && !g.toLowerCase().includes('audio book')) genres.forEach((genre) => { if (!genreListeningMap[genre]) genreListeningMap[genre] = 0 genreListeningMap[genre] += ls.timeListening || 0 }) } let topAuthors = null topAuthors = Object.keys(authorListeningMap) .map((authorName) => ({ name: authorName, time: Math.round(authorListeningMap[authorName]) })) .sort((a, b) => b.time - a.time) .slice(0, 3) let topNarrators = null topNarrators = Object.keys(narratorListeningMap) .map((narratorName) => ({ name: narratorName, time: Math.round(narratorListeningMap[narratorName]) })) .sort((a, b) => b.time - a.time) .slice(0, 3) let topGenres = null topGenres = Object.keys(genreListeningMap) .map((genre) => ({ genre, time: Math.round(genreListeningMap[genre]) })) .sort((a, b) => b.time - a.time) .slice(0, 3) // Stats for total books, size and duration for everything added this year or earlier const [totalStatResultsRow] = await Database.sequelize.query(`SELECT SUM(li.size) AS totalSize, SUM(b.duration) AS totalDuration, COUNT(*) AS totalItems FROM libraryItems li, books b WHERE b.id = li.mediaId AND li.mediaType = 'book' AND li.createdAt < ":nextYear-01-01";`, { replacements: { nextYear: year + 1 } }) const totalStatResults = totalStatResultsRow[0] return { numListeningSessions: listeningSessions.length, numBooksAdded: booksAdded.length, numAuthorsAdded, totalBooksAddedSize, totalBooksAddedDuration: Math.round(totalBooksAddedDuration), booksAddedWithCovers: booksWithCovers, totalBooksSize: totalStatResults?.totalSize || 0, totalBooksDuration: totalStatResults?.totalDuration || 0, totalListeningTime, numBooks: totalStatResults?.totalItems || 0, topAuthors, topNarrators, topGenres } } }