mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-31 15:19:42 +02:00
Merge branch 'master' into liaocl
This commit is contained in:
commit
2ec52a7a45
119 changed files with 11524 additions and 25894 deletions
|
@ -1,23 +1,28 @@
|
|||
const Path = require('path')
|
||||
const date = require('../libs/dateAndTime')
|
||||
const fs = require('../libs/fsExtra')
|
||||
const { readTextFile } = require('../utils/fileUtils')
|
||||
const fileUtils = require('../utils/fileUtils')
|
||||
const Logger = require('../Logger')
|
||||
|
||||
class DailyLog {
|
||||
constructor() {
|
||||
this.id = null
|
||||
this.datePretty = null
|
||||
/**
|
||||
*
|
||||
* @param {string} dailyLogDirPath Path to daily logs /metadata/logs/daily
|
||||
*/
|
||||
constructor(dailyLogDirPath) {
|
||||
this.id = date.format(new Date(), 'YYYY-MM-DD')
|
||||
|
||||
this.dailyLogDirPath = null
|
||||
this.filename = null
|
||||
this.path = null
|
||||
this.fullPath = null
|
||||
this.dailyLogDirPath = dailyLogDirPath
|
||||
this.filename = this.id + '.txt'
|
||||
this.fullPath = Path.join(this.dailyLogDirPath, this.filename)
|
||||
|
||||
this.createdAt = null
|
||||
this.createdAt = Date.now()
|
||||
|
||||
/** @type {import('../managers/LogManager').LogObject[]} */
|
||||
this.logs = []
|
||||
/** @type {string[]} */
|
||||
this.bufferedLogLines = []
|
||||
|
||||
this.locked = false
|
||||
}
|
||||
|
||||
|
@ -32,8 +37,6 @@ class DailyLog {
|
|||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
datePretty: this.datePretty,
|
||||
path: this.path,
|
||||
dailyLogDirPath: this.dailyLogDirPath,
|
||||
fullPath: this.fullPath,
|
||||
filename: this.filename,
|
||||
|
@ -41,36 +44,34 @@ class DailyLog {
|
|||
}
|
||||
}
|
||||
|
||||
setData(data) {
|
||||
this.id = date.format(new Date(), 'YYYY-MM-DD')
|
||||
this.datePretty = date.format(new Date(), 'ddd, MMM D YYYY')
|
||||
|
||||
this.dailyLogDirPath = data.dailyLogDirPath
|
||||
|
||||
this.filename = this.id + '.txt'
|
||||
this.path = Path.join('backups', this.filename)
|
||||
this.fullPath = Path.join(this.dailyLogDirPath, this.filename)
|
||||
|
||||
this.createdAt = Date.now()
|
||||
}
|
||||
|
||||
async appendBufferedLogs() {
|
||||
var buffered = [...this.bufferedLogLines]
|
||||
/**
|
||||
* Append all buffered lines to daily log file
|
||||
*/
|
||||
appendBufferedLogs() {
|
||||
let buffered = [...this.bufferedLogLines]
|
||||
this.bufferedLogLines = []
|
||||
|
||||
var oneBigLog = ''
|
||||
let oneBigLog = ''
|
||||
buffered.forEach((logLine) => {
|
||||
oneBigLog += logLine
|
||||
})
|
||||
this.appendLogLine(oneBigLog)
|
||||
return this.appendLogLine(oneBigLog)
|
||||
}
|
||||
|
||||
async appendLog(logObj) {
|
||||
/**
|
||||
*
|
||||
* @param {import('../managers/LogManager').LogObject} logObj
|
||||
*/
|
||||
appendLog(logObj) {
|
||||
this.logs.push(logObj)
|
||||
var line = JSON.stringify(logObj) + '\n'
|
||||
this.appendLogLine(line)
|
||||
return this.appendLogLine(JSON.stringify(logObj) + '\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* Append log to daily log file
|
||||
*
|
||||
* @param {string} line
|
||||
*/
|
||||
async appendLogLine(line) {
|
||||
if (this.locked) {
|
||||
this.bufferedLogLines.push(line)
|
||||
|
@ -84,24 +85,29 @@ class DailyLog {
|
|||
|
||||
this.locked = false
|
||||
if (this.bufferedLogLines.length) {
|
||||
this.appendBufferedLogs()
|
||||
await this.appendBufferedLogs()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all logs from file
|
||||
* Parses lines and re-saves the file if bad lines are removed
|
||||
*/
|
||||
async loadLogs() {
|
||||
var exists = await fs.pathExists(this.fullPath)
|
||||
if (!exists) {
|
||||
if (!await fs.pathExists(this.fullPath)) {
|
||||
console.error('Daily log does not exist')
|
||||
return
|
||||
}
|
||||
|
||||
var text = await readTextFile(this.fullPath)
|
||||
const text = await fileUtils.readTextFile(this.fullPath)
|
||||
|
||||
var hasFailures = false
|
||||
let hasFailures = false
|
||||
|
||||
var logLines = text.split(/\r?\n/)
|
||||
let logLines = text.split(/\r?\n/)
|
||||
// remove last log if empty
|
||||
if (logLines.length && !logLines[logLines.length - 1]) logLines = logLines.slice(0, -1)
|
||||
|
||||
// JSON parse log lines
|
||||
this.logs = logLines.map(t => {
|
||||
if (!t) {
|
||||
hasFailures = true
|
||||
|
@ -118,7 +124,7 @@ class DailyLog {
|
|||
|
||||
// Rewrite log file to remove errors
|
||||
if (hasFailures) {
|
||||
var newLogLines = this.logs.map(l => JSON.stringify(l)).join('\n') + '\n'
|
||||
const newLogLines = this.logs.map(l => JSON.stringify(l)).join('\n') + '\n'
|
||||
await fs.writeFile(this.fullPath, newLogLines)
|
||||
console.log('Re-Saved log file to remove bad lines')
|
||||
}
|
||||
|
|
|
@ -84,6 +84,24 @@ class Feed {
|
|||
return episode.fullPath
|
||||
}
|
||||
|
||||
/**
|
||||
* If chapters for an audiobook match the audio tracks then use chapter titles instead of audio file names
|
||||
*
|
||||
* @param {import('../objects/LibraryItem')} libraryItem
|
||||
* @returns {boolean}
|
||||
*/
|
||||
checkUseChapterTitlesForEpisodes(libraryItem) {
|
||||
const tracks = libraryItem.media.tracks
|
||||
const chapters = libraryItem.media.chapters
|
||||
if (tracks.length !== chapters.length) return false
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
if (Math.abs(chapters[i].start - tracks[i].startOffset) >= 1) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
setFromItem(userId, slug, libraryItem, serverAddress, preventIndexing = true, ownerName = null, ownerEmail = null) {
|
||||
const media = libraryItem.media
|
||||
const mediaMetadata = media.metadata
|
||||
|
@ -128,9 +146,10 @@ class Feed {
|
|||
this.episodes.push(feedEpisode)
|
||||
})
|
||||
} else { // AUDIOBOOK EPISODES
|
||||
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(libraryItem)
|
||||
media.tracks.forEach((audioTrack) => {
|
||||
const feedEpisode = new FeedEpisode()
|
||||
feedEpisode.setFromAudiobookTrack(libraryItem, serverAddress, slug, audioTrack, this.meta)
|
||||
feedEpisode.setFromAudiobookTrack(libraryItem, serverAddress, slug, audioTrack, this.meta, useChapterTitles)
|
||||
this.episodes.push(feedEpisode)
|
||||
})
|
||||
}
|
||||
|
@ -168,9 +187,10 @@ class Feed {
|
|||
this.episodes.push(feedEpisode)
|
||||
})
|
||||
} else { // AUDIOBOOK EPISODES
|
||||
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(libraryItem)
|
||||
media.tracks.forEach((audioTrack) => {
|
||||
const feedEpisode = new FeedEpisode()
|
||||
feedEpisode.setFromAudiobookTrack(libraryItem, this.serverAddress, this.slug, audioTrack, this.meta)
|
||||
feedEpisode.setFromAudiobookTrack(libraryItem, this.serverAddress, this.slug, audioTrack, this.meta, useChapterTitles)
|
||||
this.episodes.push(feedEpisode)
|
||||
})
|
||||
}
|
||||
|
@ -214,9 +234,10 @@ class Feed {
|
|||
itemsWithTracks.forEach((item, index) => {
|
||||
if (item.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = item.updatedAt
|
||||
|
||||
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(item)
|
||||
item.media.tracks.forEach((audioTrack) => {
|
||||
const feedEpisode = new FeedEpisode()
|
||||
feedEpisode.setFromAudiobookTrack(item, serverAddress, slug, audioTrack, this.meta, index)
|
||||
feedEpisode.setFromAudiobookTrack(item, serverAddress, slug, audioTrack, this.meta, useChapterTitles, index)
|
||||
this.episodes.push(feedEpisode)
|
||||
})
|
||||
})
|
||||
|
@ -245,9 +266,10 @@ class Feed {
|
|||
itemsWithTracks.forEach((item, index) => {
|
||||
if (item.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = item.updatedAt
|
||||
|
||||
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(item)
|
||||
item.media.tracks.forEach((audioTrack) => {
|
||||
const feedEpisode = new FeedEpisode()
|
||||
feedEpisode.setFromAudiobookTrack(item, this.serverAddress, this.slug, audioTrack, this.meta, index)
|
||||
feedEpisode.setFromAudiobookTrack(item, this.serverAddress, this.slug, audioTrack, this.meta, useChapterTitles, index)
|
||||
this.episodes.push(feedEpisode)
|
||||
})
|
||||
})
|
||||
|
@ -295,9 +317,10 @@ class Feed {
|
|||
itemsWithTracks.forEach((item, index) => {
|
||||
if (item.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = item.updatedAt
|
||||
|
||||
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(item)
|
||||
item.media.tracks.forEach((audioTrack) => {
|
||||
const feedEpisode = new FeedEpisode()
|
||||
feedEpisode.setFromAudiobookTrack(item, serverAddress, slug, audioTrack, this.meta, index)
|
||||
feedEpisode.setFromAudiobookTrack(item, serverAddress, slug, audioTrack, this.meta, useChapterTitles, index)
|
||||
this.episodes.push(feedEpisode)
|
||||
})
|
||||
})
|
||||
|
@ -329,9 +352,10 @@ class Feed {
|
|||
itemsWithTracks.forEach((item, index) => {
|
||||
if (item.updatedAt > this.entityUpdatedAt) this.entityUpdatedAt = item.updatedAt
|
||||
|
||||
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(item)
|
||||
item.media.tracks.forEach((audioTrack) => {
|
||||
const feedEpisode = new FeedEpisode()
|
||||
feedEpisode.setFromAudiobookTrack(item, this.serverAddress, this.slug, audioTrack, this.meta, index)
|
||||
feedEpisode.setFromAudiobookTrack(item, this.serverAddress, this.slug, audioTrack, this.meta, useChapterTitles, index)
|
||||
this.episodes.push(feedEpisode)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -97,7 +97,17 @@ class FeedEpisode {
|
|||
this.fullPath = episode.audioFile.metadata.path
|
||||
}
|
||||
|
||||
setFromAudiobookTrack(libraryItem, serverAddress, slug, audioTrack, meta, additionalOffset = null) {
|
||||
/**
|
||||
*
|
||||
* @param {import('../objects/LibraryItem')} libraryItem
|
||||
* @param {string} serverAddress
|
||||
* @param {string} slug
|
||||
* @param {import('../objects/files/AudioTrack')} audioTrack
|
||||
* @param {Object} meta
|
||||
* @param {boolean} useChapterTitles
|
||||
* @param {number} [additionalOffset]
|
||||
*/
|
||||
setFromAudiobookTrack(libraryItem, serverAddress, slug, audioTrack, meta, useChapterTitles, additionalOffset = null) {
|
||||
// Example: <pubDate>Fri, 04 Feb 2015 00:00:00 GMT</pubDate>
|
||||
let timeOffset = isNaN(audioTrack.index) ? 0 : (Number(audioTrack.index) * 1000) // Offset pubdate to ensure correct order
|
||||
let episodeId = uuidv4()
|
||||
|
@ -119,10 +129,10 @@ class FeedEpisode {
|
|||
if (libraryItem.media.tracks.length == 1) { // If audiobook is a single file, use book title instead of chapter/file title
|
||||
title = libraryItem.media.metadata.title
|
||||
} else {
|
||||
if (libraryItem.media.chapters.length) {
|
||||
if (useChapterTitles) {
|
||||
// If audio track start and chapter start are within 1 seconds of eachother then use the chapter title
|
||||
var matchingChapter = libraryItem.media.chapters.find(ch => Math.abs(ch.start - audioTrack.startOffset) < 1)
|
||||
if (matchingChapter && matchingChapter.title) title = matchingChapter.title
|
||||
const matchingChapter = libraryItem.media.chapters.find(ch => Math.abs(ch.start - audioTrack.startOffset) < 1)
|
||||
if (matchingChapter?.title) title = matchingChapter.title
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ class ServerSettings {
|
|||
this.buildNumber = packageJson.buildNumber
|
||||
|
||||
// Auth settings
|
||||
// Active auth methodes
|
||||
this.authLoginCustomMessage = null
|
||||
this.authActiveAuthMethods = ['local']
|
||||
|
||||
// openid settings
|
||||
|
@ -113,6 +113,7 @@ class ServerSettings {
|
|||
this.version = settings.version || null
|
||||
this.buildNumber = settings.buildNumber || 0 // Added v2.4.5
|
||||
|
||||
this.authLoginCustomMessage = settings.authLoginCustomMessage || null // Added v2.7.3
|
||||
this.authActiveAuthMethods = settings.authActiveAuthMethods || ['local']
|
||||
|
||||
this.authOpenIDIssuerURL = settings.authOpenIDIssuerURL || null
|
||||
|
@ -201,6 +202,7 @@ class ServerSettings {
|
|||
logLevel: this.logLevel,
|
||||
version: this.version,
|
||||
buildNumber: this.buildNumber,
|
||||
authLoginCustomMessage: this.authLoginCustomMessage,
|
||||
authActiveAuthMethods: this.authActiveAuthMethods,
|
||||
authOpenIDIssuerURL: this.authOpenIDIssuerURL,
|
||||
authOpenIDAuthorizationURL: this.authOpenIDAuthorizationURL,
|
||||
|
@ -213,7 +215,7 @@ class ServerSettings {
|
|||
authOpenIDButtonText: this.authOpenIDButtonText,
|
||||
authOpenIDAutoLaunch: this.authOpenIDAutoLaunch,
|
||||
authOpenIDAutoRegister: this.authOpenIDAutoRegister,
|
||||
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy,
|
||||
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy,
|
||||
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs // Do not return to client
|
||||
}
|
||||
}
|
||||
|
@ -246,6 +248,7 @@ class ServerSettings {
|
|||
|
||||
get authenticationSettings() {
|
||||
return {
|
||||
authLoginCustomMessage: this.authLoginCustomMessage,
|
||||
authActiveAuthMethods: this.authActiveAuthMethods,
|
||||
authOpenIDIssuerURL: this.authOpenIDIssuerURL,
|
||||
authOpenIDAuthorizationURL: this.authOpenIDAuthorizationURL,
|
||||
|
@ -264,7 +267,9 @@ class ServerSettings {
|
|||
}
|
||||
|
||||
get authFormData() {
|
||||
const clientFormData = {}
|
||||
const clientFormData = {
|
||||
authLoginCustomMessage: this.authLoginCustomMessage
|
||||
}
|
||||
if (this.authActiveAuthMethods.includes('openid')) {
|
||||
clientFormData.authOpenIDButtonText = this.authOpenIDButtonText
|
||||
clientFormData.authOpenIDAutoLaunch = this.authOpenIDAutoLaunch
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue