2023-12-23 00:01:07 +01:00
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 )
2023-12-23 22:29:34 +01:00
let authorListeningMap = { }
let narratorListeningMap = { }
let genreListeningMap = { }
2023-12-23 00:01:07 +01:00
const listeningSessions = await this . getListeningSessionsForYear ( year )
let totalListeningTime = 0
2023-12-23 22:29:34 +01:00
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"
2024-02-16 23:16:55 +01:00
const genres = ( ls . mediaMetadata . genres || [ ] ) . filter ( g => g && ! g . toLowerCase ( ) . includes ( 'audiobook' ) && ! g . toLowerCase ( ) . includes ( 'audio book' ) )
2023-12-23 22:29:34 +01:00
genres . forEach ( ( genre ) => {
if ( ! genreListeningMap [ genre ] ) genreListeningMap [ genre ] = 0
genreListeningMap [ genre ] += ( ls . timeListening || 0 )
} )
2023-12-23 00:01:07 +01:00
}
2023-12-23 22:29:34 +01:00
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 )
2023-12-23 00:01:07 +01:00
// 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 ,
2023-12-23 22:29:34 +01:00
numBooks : totalStatResults ? . totalItems || 0 ,
topAuthors ,
topNarrators ,
topGenres
2023-12-23 00:01:07 +01:00
}
}
}