From fd0af6b2dddce3f951f5a27da8e9568baaaeb59c Mon Sep 17 00:00:00 2001 From: Jaffar Ashoor Date: Sun, 23 Mar 2025 02:59:40 +0300 Subject: [PATCH 01/70] Reduce final docker image size this adds a third stage to the build, copying the required files only from the previos stages, this reduces the final image size from 600MB+ down to ~320MB --- Dockerfile | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4e110a619..3eea9cd5f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ ### STAGE 0: Build client ### -FROM node:20-alpine AS build +FROM node:20-alpine AS build-client WORKDIR /client COPY /client /client RUN npm ci && npm cache clean --force RUN npm run generate ### STAGE 1: Build server ### -FROM node:20-alpine +FROM node:20-alpine AS build-server ENV NODE_ENV=production @@ -21,9 +21,9 @@ RUN apk update && \ tini \ unzip -COPY --from=build /client/dist /client/dist -COPY index.js package* / -COPY server server +WORKDIR /server +COPY index.js package* /server +COPY /server /server/server ARG TARGETPLATFORM @@ -42,7 +42,20 @@ RUN case "$TARGETPLATFORM" in \ RUN npm ci --only=production -RUN apk del make python3 g++ +### STAGE 2: Create minimal runtime image ### +FROM node:20-alpine + +# Install only runtime dependencies +RUN apk add --no-cache \ + tzdata \ + ffmpeg \ + tini + +WORKDIR /app + +# Copy compiled frontend and server from build stages +COPY --from=build-client /client/dist /app/client/dist +COPY --from=build-server /server /app EXPOSE 80 From 13fac2d5bcf58c7a2225b7ff7adde9ff1318f459 Mon Sep 17 00:00:00 2001 From: Balki Date: Tue, 18 Mar 2025 18:20:05 +0000 Subject: [PATCH 02/70] Support http server listening on unix socket --- server/Server.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/server/Server.js b/server/Server.js index c3e73aec8..fde5343ef 100644 --- a/server/Server.js +++ b/server/Server.js @@ -395,10 +395,19 @@ class Server { }) router.get('/healthcheck', (req, res) => res.sendStatus(200)) - this.server.listen(this.Port, this.Host, () => { - if (this.Host) Logger.info(`Listening on http://${this.Host}:${this.Port}`) - else Logger.info(`Listening on port :${this.Port}`) - }) + const unixSocketPrefix = "unix/" + if(this.Host.startsWith(unixSocketPrefix)) { + const sockPath = this.Host.slice(unixSocketPrefix.length) + this.server.listen(sockPath, () => { + fs.chmodSync(sockPath, 0o666) + Logger.info(`Listening on unix socket ${sockPath}`) + }) + } else { + this.server.listen(this.Port, this.Host, () => { + if (this.Host) Logger.info(`Listening on http://${this.Host}:${this.Port}`) + else Logger.info(`Listening on port :${this.Port}`) + }) + } // Start listening for socket connections SocketAuthority.initialize(this) From a2dc76e1902c8664dcfb836a1a13ef5aed38d5ea Mon Sep 17 00:00:00 2001 From: Vito0912 <86927734+Vito0912@users.noreply.github.com> Date: Sun, 27 Apr 2025 19:21:37 +0200 Subject: [PATCH 03/70] remove brading --- client/pages/audiobook/_id/chapters.vue | 51 +++++++++++++++++++++---- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/client/pages/audiobook/_id/chapters.vue b/client/pages/audiobook/_id/chapters.vue index 55f74b5c2..659e4e580 100644 --- a/client/pages/audiobook/_id/chapters.vue +++ b/client/pages/audiobook/_id/chapters.vue @@ -144,18 +144,20 @@
- - - {{ $strings.ButtonSearch }} + + + {{ $strings.ButtonSearch }} +
+
+
-

{{ asinError }}

{{ $strings.MessageAsinCheck }}

- - +
+
@@ -261,6 +263,7 @@ export default { showFindChaptersModal: false, chapterData: null, asinError: null, + removeBranding: false, showSecondInputs: false, audibleRegions: ['US', 'CA', 'UK', 'AU', 'FR', 'DE', 'JP', 'IT', 'IN', 'ES'], hasChanges: false @@ -322,6 +325,9 @@ export default { this.checkChapters() }, + toggleRemoveBranding() { + this.removeBranding = !this.removeBranding; + }, shiftChapterTimes() { if (!this.shiftAmount || isNaN(this.shiftAmount) || this.newChapters.length <= 1) { return @@ -568,7 +574,7 @@ export default { this.asinError = this.$getString(data.stringKey) } else { console.log('Chapter data', data) - this.chapterData = data + this.chapterData = this.removeBranding ? this.removeBrandingFromData(data) : data } }) .catch((error) => { @@ -578,6 +584,37 @@ export default { this.showFindChaptersModal = false }) }, + removeBrandingFromData(data) { + if (!data) return data + try { + const introDuration = data.brandIntroDurationMs + const outroDuration = data.brandOutroDurationMs + + for (let i = 0; i < data.chapters.length; i++) { + const chapter = data.chapters[i] + if (chapter.startOffsetMs < introDuration) { + // This should never happen, as the intro is not longer than the first chapter + // If this happens set to the next second + // Will be 0 for the first chapter anayways + chapter.startOffsetMs = i * 1000 + chapter.startOffsetSec = i + } else { + chapter.startOffsetMs -= introDuration + chapter.startOffsetSec = Math.floor(chapter.startOffsetMs / 1000) + } + } + + const lastChapter = data.chapters[data.chapters.length - 1] + // If there is an outro that's in the outro duration, remove it + if (lastChapter && lastChapter.lengthMs <= outroDuration) { + data.chapters.pop() + } + + } catch { + return data + } + return data + }, resetChapters() { const payload = { message: this.$strings.MessageResetChaptersConfirm, From 38957d4f323cceaad92d1751fe2116f426e763f7 Mon Sep 17 00:00:00 2001 From: Vito0912 <86927734+Vito0912@users.noreply.github.com> Date: Sun, 27 Apr 2025 19:34:12 +0200 Subject: [PATCH 04/70] fix shift times not works when editing --- client/pages/audiobook/_id/chapters.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/pages/audiobook/_id/chapters.vue b/client/pages/audiobook/_id/chapters.vue index 659e4e580..94602e8da 100644 --- a/client/pages/audiobook/_id/chapters.vue +++ b/client/pages/audiobook/_id/chapters.vue @@ -341,8 +341,8 @@ export default { return } - if (this.newChapters[0].end + amount <= 0) { - this.$toast.error('Invalid shift amount. First chapter would have zero or negative length.') + if (this.newChapters[1].start + amount <= 0) { + this.$toast.error('Invalid shift amount. The first chapter would have zero or negative length and would be overwritten by the second chapter. Increase the start duration of second chapter. ') return } From 65aec6a0993f39481b958e02a81d5382f3d76184 Mon Sep 17 00:00:00 2001 From: Vito0912 <86927734+Vito0912@users.noreply.github.com> Date: Sun, 27 Apr 2025 19:44:28 +0200 Subject: [PATCH 05/70] Adds locale --- client/pages/audiobook/_id/chapters.vue | 6 +++--- client/strings/en-us.json | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/client/pages/audiobook/_id/chapters.vue b/client/pages/audiobook/_id/chapters.vue index 94602e8da..3c6fe4766 100644 --- a/client/pages/audiobook/_id/chapters.vue +++ b/client/pages/audiobook/_id/chapters.vue @@ -149,7 +149,7 @@ {{ $strings.ButtonSearch }}
- +

{{ asinError }}

@@ -337,12 +337,12 @@ export default { const lastChapter = this.newChapters[this.newChapters.length - 1] if (lastChapter.start + amount > this.mediaDurationRounded) { - this.$toast.error('Invalid shift amount. Last chapter start time would extend beyond the duration of this audiobook.') + this.$toast.error($strings.ToastInvalidShiftAmountLast) return } if (this.newChapters[1].start + amount <= 0) { - this.$toast.error('Invalid shift amount. The first chapter would have zero or negative length and would be overwritten by the second chapter. Increase the start duration of second chapter. ') + this.$toast.error($strings.ToastInvalidShiftAmountStart) return } diff --git a/client/strings/en-us.json b/client/strings/en-us.json index 2bf70b53c..101ee161e 100644 --- a/client/strings/en-us.json +++ b/client/strings/en-us.json @@ -530,6 +530,7 @@ "LabelReleaseDate": "Release Date", "LabelRemoveAllMetadataAbs": "Remove all metadata.abs files", "LabelRemoveAllMetadataJson": "Remove all metadata.json files", + "LabelRemoveAudibleBranding": "Remove Audible intro and outro from chapters", "LabelRemoveCover": "Remove cover", "LabelRemoveMetadataFile": "Remove metadata files in library item folders", "LabelRemoveMetadataFileHelp": "Remove all metadata.json and metadata.abs files in your {0} folders.", @@ -998,6 +999,8 @@ "ToastFailedToUpdate": "Failed to update", "ToastInvalidImageUrl": "Invalid image URL", "ToastInvalidMaxEpisodesToDownload": "Invalid max episodes to download", + "ToastInvalidShiftAmountLast": "Invalid shift amount. The last chapter start time would extend beyond the duration of this audiobook.", + "ToastInvalidShiftAmountStart": "Invalid shift amount. The first chapter would have zero or negative length and would be overwritten by the second chapter. Increase the start duration of second chapter.", "ToastInvalidUrl": "Invalid URL", "ToastItemCoverUpdateSuccess": "Item cover updated", "ToastItemDeletedFailed": "Failed to delete item", From a1074e69ac2addcfbb4ab3696587326fb2f20285 Mon Sep 17 00:00:00 2001 From: Vito0912 <86927734+Vito0912@users.noreply.github.com> Date: Sun, 27 Apr 2025 19:51:56 +0200 Subject: [PATCH 06/70] Fixed crash --- client/pages/audiobook/_id/chapters.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/pages/audiobook/_id/chapters.vue b/client/pages/audiobook/_id/chapters.vue index 3c6fe4766..085b1584c 100644 --- a/client/pages/audiobook/_id/chapters.vue +++ b/client/pages/audiobook/_id/chapters.vue @@ -337,12 +337,12 @@ export default { const lastChapter = this.newChapters[this.newChapters.length - 1] if (lastChapter.start + amount > this.mediaDurationRounded) { - this.$toast.error($strings.ToastInvalidShiftAmountLast) + this.$toast.error(this.$strings.ToastInvalidShiftAmountLast) return } if (this.newChapters[1].start + amount <= 0) { - this.$toast.error($strings.ToastInvalidShiftAmountStart) + this.$toast.error(this.$strings.ToastInvalidShiftAmountStart) return } From 2cc9d1b7f8f42fa3f8965343851b7c673060555e Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 1 May 2025 17:17:40 -0500 Subject: [PATCH 07/70] Update watcher to re-scan library items when non-media files are added/updated #4245 --- server/models/LibraryItem.js | 1 - server/scanner/LibraryScanner.js | 2 +- server/utils/scandir.js | 22 ++++++++++++++++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/server/models/LibraryItem.js b/server/models/LibraryItem.js index bf561d5ef..16a521615 100644 --- a/server/models/LibraryItem.js +++ b/server/models/LibraryItem.js @@ -246,7 +246,6 @@ class LibraryItem extends Model { include }) if (!libraryItem) { - Logger.error(`[LibraryItem] Library item not found`) return null } diff --git a/server/scanner/LibraryScanner.js b/server/scanner/LibraryScanner.js index 4d0285dd5..bc174d7a0 100644 --- a/server/scanner/LibraryScanner.js +++ b/server/scanner/LibraryScanner.js @@ -407,7 +407,7 @@ class LibraryScanner { const folder = library.libraryFolders[0] const filePathItems = folderGroups[folderId].fileUpdates.map((fileUpdate) => fileUtils.getFilePathItemFromFileUpdate(fileUpdate)) - const fileUpdateGroup = scanUtils.groupFileItemsIntoLibraryItemDirs(library.mediaType, filePathItems, !!library.settings?.audiobooksOnly) + const fileUpdateGroup = scanUtils.groupFileItemsIntoLibraryItemDirs(library.mediaType, filePathItems, !!library.settings?.audiobooksOnly, true) if (!Object.keys(fileUpdateGroup).length) { Logger.info(`[LibraryScanner] No important changes to scan for in folder "${folderId}"`) diff --git a/server/utils/scandir.js b/server/utils/scandir.js index 0aaa5e195..6dd2d67fe 100644 --- a/server/utils/scandir.js +++ b/server/utils/scandir.js @@ -24,6 +24,12 @@ function isMediaFile(mediaType, ext, audiobooksOnly = false) { return globals.SupportedAudioTypes.includes(extclean) || globals.SupportedEbookTypes.includes(extclean) } +function isScannableNonMediaFile(ext) { + if (!ext) return false + const extclean = ext.slice(1).toLowerCase() + return globals.TextFileTypes.includes(extclean) || globals.MetadataFileTypes.includes(extclean) || globals.SupportedImageTypes.includes(extclean) +} + function checkFilepathIsAudioFile(filepath) { const ext = Path.extname(filepath) if (!ext) return false @@ -35,27 +41,31 @@ module.exports.checkFilepathIsAudioFile = checkFilepathIsAudioFile /** * @param {string} mediaType * @param {import('./fileUtils').FilePathItem[]} fileItems - * @param {boolean} [audiobooksOnly=false] + * @param {boolean} audiobooksOnly + * @param {boolean} [includeNonMediaFiles=false] - Used by the watcher to re-scan when covers/metadata files are added/removed * @returns {Record} map of files grouped into potential libarary item dirs */ -function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly = false) { +function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly, includeNonMediaFiles = false) { // Step 1: Filter out non-book-media files in root dir (with depth of 0) const itemsFiltered = fileItems.filter((i) => { return i.deep > 0 || (mediaType === 'book' && isMediaFile(mediaType, i.extension, audiobooksOnly)) }) // Step 2: Separate media files and other files - // - Directories without a media file will not be included + // - Directories without a media file will not be included (unless includeNonMediaFiles is true) /** @type {import('./fileUtils').FilePathItem[]} */ const mediaFileItems = [] /** @type {import('./fileUtils').FilePathItem[]} */ const otherFileItems = [] itemsFiltered.forEach((item) => { - if (isMediaFile(mediaType, item.extension, audiobooksOnly)) mediaFileItems.push(item) - else otherFileItems.push(item) + if (isMediaFile(mediaType, item.extension, audiobooksOnly) || (includeNonMediaFiles && isScannableNonMediaFile(item.extension))) { + mediaFileItems.push(item) + } else { + otherFileItems.push(item) + } }) - // Step 3: Group audio files in library items + // Step 3: Group media files (or non-media files if includeNonMediaFiles is true) in library items const libraryItemGroup = {} mediaFileItems.forEach((item) => { const dirparts = item.reldirpath.split('/').filter((p) => !!p) From e0b90c6813defde58ef8944415cdb84b1c6379a7 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 2 May 2025 15:06:31 -0500 Subject: [PATCH 08/70] Add channels, codec and bitrate to tracks table & breakpoint updates --- .gitignore | 1 + client/pages/audiobook/_id/manage.vue | 24 +++++++++++++++------ server/controllers/LibraryItemController.js | 4 ++-- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index d375bae08..12ebec1c2 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ sw.* .DS_STORE .idea/* tailwind.compiled.css +tailwind.config.js diff --git a/client/pages/audiobook/_id/manage.vue b/client/pages/audiobook/_id/manage.vue index 3d9ac0512..cc86398bb 100644 --- a/client/pages/audiobook/_id/manage.vue +++ b/client/pages/audiobook/_id/manage.vue @@ -18,8 +18,8 @@
-
-
+
+
{{ $strings.LabelMetaTag }}
{{ $strings.LabelValue }}
@@ -35,7 +35,7 @@
-
+
{{ $strings.LabelChapterTitle }}
{{ $strings.LabelStart }}
@@ -146,19 +146,29 @@
#
{{ $strings.LabelFilename }}
+ + +
{{ $strings.LabelSize }}