Also exclude disabled tracks from book duration on rescan

BookScanner recomputed media.duration by summing all audio files, so a
rescan that detected any file change would re-count a previously disabled
(excluded) track and re-inflate the duration. Use the same
Book.updateDuration() (included tracks only) so both the manage-tracks
path and rescans stay consistent. Add a Book model unit test for
updateDuration().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
wakamex 2026-06-29 19:40:45 -04:00
parent 9cfe9964c2
commit 067d3f6394
2 changed files with 64 additions and 6 deletions

View file

@ -136,12 +136,8 @@ class BookScanner {
media.audioFiles = AudioFileScanner.runSmartTrackOrder(existingLibraryItem.relPath, media.audioFiles) media.audioFiles = AudioFileScanner.runSmartTrackOrder(existingLibraryItem.relPath, media.audioFiles)
media.duration = 0 // Recompute duration from included (non-excluded) tracks so disabled tracks don't get re-counted on rescan
media.audioFiles.forEach((af) => { media.updateDuration()
if (!isNaN(af.duration)) {
media.duration += af.duration
}
})
media.changed('audioFiles', true) media.changed('audioFiles', true)
} }

View file

@ -0,0 +1,62 @@
const { expect } = require('chai')
const { Sequelize } = require('sequelize')
const Database = require('../../../server/Database')
describe('Book', () => {
beforeEach(async () => {
global.ServerSettings = {}
Database.sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
Database.sequelize.uppercaseFirst = (str) => (str ? `${str[0].toUpperCase()}${str.substr(1)}` : '')
await Database.buildModels()
})
afterEach(async () => {
await Database.sequelize.sync({ force: true })
})
describe('updateDuration', () => {
it('sums only the included (non-excluded) audio files', () => {
const book = Database.bookModel.build({
title: 'Test Book',
duration: 0,
audioFiles: [
{ ino: '1', duration: 100, exclude: false },
{ ino: '2', duration: 50, exclude: true },
{ ino: '3', duration: 25, exclude: false }
]
})
const changed = book.updateDuration()
expect(book.duration).to.equal(125) // 100 + 25, the excluded 50 is ignored
expect(changed).to.be.true
})
it('returns false and leaves duration unchanged when already correct', () => {
const book = Database.bookModel.build({
title: 'Test Book',
duration: 100,
audioFiles: [{ ino: '1', duration: 100, exclude: false }]
})
expect(book.updateDuration()).to.be.false
expect(book.duration).to.equal(100)
})
it('ignores audio files with a non-numeric duration', () => {
const book = Database.bookModel.build({
title: 'Test Book',
duration: 0,
audioFiles: [
{ ino: '1', duration: 100, exclude: false },
{ ino: '2', duration: NaN, exclude: false }
]
})
book.updateDuration()
expect(book.duration).to.equal(100)
})
})
})