mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
172 lines
5.3 KiB
JavaScript
172 lines
5.3 KiB
JavaScript
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<PlaybackSession[]>}
|
|
*/
|
|
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<number>}
|
|
*/
|
|
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<import('../../models/Book')[]>}
|
|
*/
|
|
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
|
|
}
|
|
}
|
|
}
|