audiobookshelf/server/utils/audioFileScanner.js
2021-09-08 09:15:54 -05:00

168 lines
5.1 KiB
JavaScript

const Path = require('path')
const Logger = require('../Logger')
const prober = require('./prober')
function getDefaultAudioStream(audioStreams) {
if (audioStreams.length === 1) return audioStreams[0]
var defaultStream = audioStreams.find(a => a.is_default)
if (!defaultStream) return audioStreams[0]
return defaultStream
}
async function scan(path) {
var probeData = await prober(path)
if (!probeData || !probeData.audio_streams || !probeData.audio_streams.length) {
return {
error: 'Invalid audio file'
}
}
if (!probeData.duration || !probeData.size) {
return {
error: 'Invalid duration or size'
}
}
var audioStream = getDefaultAudioStream(probeData.audio_streams)
const finalData = {
format: probeData.format,
duration: probeData.duration,
size: probeData.size,
bit_rate: audioStream.bit_rate || probeData.bit_rate,
codec: audioStream.codec,
time_base: audioStream.time_base,
language: audioStream.language,
channel_layout: audioStream.channel_layout,
channels: audioStream.channels,
sample_rate: audioStream.sample_rate,
chapters: probeData.chapters || []
}
for (const key in probeData) {
if (probeData[key] && key.startsWith('file_tag')) {
finalData[key] = probeData[key]
}
}
if (finalData.file_tag_track) {
var track = finalData.file_tag_track
var trackParts = track.split('/').map(part => Number(part))
if (trackParts.length > 0) {
finalData.trackNumber = trackParts[0]
}
if (trackParts.length > 1) {
finalData.trackTotal = trackParts[1]
}
}
return finalData
}
module.exports.scan = scan
function isNumber(val) {
return !isNaN(val) && val !== null
}
function getTrackNumberFromMeta(scanData) {
return !isNaN(scanData.trackNumber) && scanData.trackNumber !== null ? Math.trunc(Number(scanData.trackNumber)) : null
}
function getTrackNumberFromFilename(title, author, series, publishYear, filename) {
var partbasename = Path.basename(filename, Path.extname(filename))
// Remove title, author, series, and publishYear from filename if there
if (title) partbasename = partbasename.replace(title, '')
if (author) partbasename = partbasename.replace(author, '')
if (series) partbasename = partbasename.replace(series, '')
if (publishYear) partbasename = partbasename.replace(publishYear)
var numbersinpath = partbasename.match(/\d+/g)
if (!numbersinpath) return null
var number = numbersinpath.length ? parseInt(numbersinpath[0]) : null
return number
}
async function scanAudioFiles(audiobook, newAudioFiles) {
if (!newAudioFiles || !newAudioFiles.length) {
Logger.error('[AudioFileScanner] Scan Audio Files no files', audiobook.title)
return
}
var tracks = []
for (let i = 0; i < newAudioFiles.length; i++) {
var audioFile = newAudioFiles[i]
var scanData = await scan(audioFile.fullPath)
if (!scanData || scanData.error) {
Logger.error('[AudioFileScanner] Scan failed for', audioFile.path)
// audiobook.invalidAudioFiles.push(parts[i])
continue;
}
var trackNumFromMeta = getTrackNumberFromMeta(scanData)
var book = audiobook.book || {}
var trackNumFromFilename = getTrackNumberFromFilename(book.title, book.author, book.series, book.publishYear, audioFile.filename)
var audioFileObj = {
ino: audioFile.ino,
filename: audioFile.filename,
path: audioFile.path,
fullPath: audioFile.fullPath,
ext: audioFile.ext,
...scanData,
trackNumFromMeta,
trackNumFromFilename
}
var audioFile = audiobook.addAudioFile(audioFileObj)
var trackNumber = 1
if (newAudioFiles.length > 1) {
trackNumber = isNumber(trackNumFromMeta) ? trackNumFromMeta : trackNumFromFilename
if (trackNumber === null) {
Logger.error('[AudioFileScanner] Invalid track number for', audioFile.filename)
audioFile.invalid = true
audioFile.error = 'Failed to get track number'
continue;
}
}
if (tracks.find(t => t.index === trackNumber)) {
Logger.error('[AudioFileScanner] Duplicate track number for', audioFile.filename)
audioFile.invalid = true
audioFile.error = 'Duplicate track number'
continue;
}
audioFile.index = trackNumber
tracks.push(audioFile)
}
if (!tracks.length) {
Logger.warn('[AudioFileScanner] No Tracks for audiobook', audiobook.id)
return
}
tracks.sort((a, b) => a.index - b.index)
audiobook.audioFiles.sort((a, b) => {
var aNum = isNumber(a.trackNumFromMeta) ? a.trackNumFromMeta : isNumber(a.trackNumFromFilename) ? a.trackNumFromFilename : 0
var bNum = isNumber(b.trackNumFromMeta) ? b.trackNumFromMeta : isNumber(b.trackNumFromFilename) ? b.trackNumFromFilename : 0
return aNum - bNum
})
// If first index is 0, increment all by 1
if (tracks[0].index === 0) {
tracks = tracks.map(t => {
t.index += 1
return t
})
}
var hasTracksAlready = audiobook.tracks.length
tracks.forEach((track) => {
audiobook.addTrack(track)
})
if (hasTracksAlready) {
audiobook.tracks.sort((a, b) => a.index - b.index)
}
}
module.exports.scanAudioFiles = scanAudioFiles