mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-28 09:12:25 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			163 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			5.2 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
 | |
|     }
 | |
|   }
 | |
| }
 |