diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d78d4c990e..c383c19086 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -16,7 +16,7 @@ on:
- "src/Readarr.Api.*/openapi.json"
env:
- DOTNET_VERSION: "6.0.427"
+ DOTNET_VERSION: "8.0.423"
READARR_VERSION: 0.4.20
OUTPUT_FOLDER: ./_output
ARTIFACTS_FOLDER: ./_artifacts
@@ -85,7 +85,7 @@ jobs:
- name: Prepare tests
run: |
mkdir -p _tests/bin
- cp _output/net6.0/linux-x64/publish/Readarr _tests/bin/
+ cp _output/net8.0/linux-x64/publish/Readarr _tests/bin/
chmod +x _tests/bin/Readarr
find _tests -name "Readarr.Test.Dummy" -exec chmod a+x {} \;
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000000..644cb37ff0
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,78 @@
+name: Publish images
+
+permissions:
+ contents: read
+ packages: write
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [main, feat/net8]
+ paths-ignore:
+ - "**.md"
+
+jobs:
+ lectarr:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+
+ - uses: docker/setup-buildx-action@v3
+
+ - uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - uses: docker/metadata-action@v5
+ id: meta
+ with:
+ images: ghcr.io/${{ github.repository }}
+ tags: |
+ type=ref,event=branch
+ type=sha
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: deploy/Dockerfile
+ platforms: linux/amd64
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha,scope=lectarr
+ cache-to: type=gha,scope=lectarr,mode=max
+
+ metadata-proxy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+
+ - uses: docker/setup-buildx-action@v3
+
+ - uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - uses: docker/metadata-action@v5
+ id: meta
+ with:
+ images: ghcr.io/${{ github.repository }}-metadata
+ tags: |
+ type=ref,event=branch
+ type=sha
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - uses: docker/build-push-action@v6
+ with:
+ context: deploy/rreading-glasses
+ platforms: linux/amd64
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha,scope=metadata
+ cache-to: type=gha,scope=metadata,mode=max
diff --git a/.vscode/launch.json b/.vscode/launch.json
index f7b3e0c830..502beb8db0 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -10,7 +10,7 @@
"request": "launch",
"preLaunchTask": "build dotnet",
// If you have changed target frameworks, make sure to update the program path.
- "program": "${workspaceFolder}/_output/net6.0/Readarr",
+ "program": "${workspaceFolder}/_output/net8.0/Readarr",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
diff --git a/README.md b/README.md
index f9391a9941..582f58c130 100644
--- a/README.md
+++ b/README.md
@@ -76,6 +76,16 @@ messages are in English.*
raisons principales pour lesquelles les éditions FR n'apparaissaient jamais.
(Patch également proposé upstream.)
+### Conversion de format
+
+- **Format préféré** (Settings → Media Management → Book Conversion) : chaque
+ ebook importé dans un autre format est automatiquement converti (epub, mobi,
+ azw3, pdf) via calibre, avec les métadonnées propres (titre de l'édition,
+ auteur, ISBN, langue) intégrées au fichier. Au choix : conserver l'original
+ ou l'envoyer à la corbeille. Nécessite le CLI calibre — décommenter
+ `DOCKER_MODS: linuxserver/mods:universal-calibre` dans le compose (un
+ avertissement de santé s'affiche si la conversion est activée sans lui).
+
## Démarrage rapide
```bash
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index b6c9a5098c..2523fa7a39 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -15,7 +15,7 @@ variables:
buildName: '$(Build.SourceBranchName).$(readarrVersion)'
sentryOrg: 'servarr'
sentryUrl: 'https://sentry.servarr.com'
- dotnetVersion: '6.0.427'
+ dotnetVersion: '8.0.423'
nodeVersion: '20.X'
innoVersion: '6.2.0'
windowsImage: 'windows-2022'
@@ -122,27 +122,27 @@ stages:
artifact: '$(osName)Backend'
displayName: Publish Backend
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- - publish: '$(testsFolder)/net6.0/win-x64/publish'
+ - publish: '$(testsFolder)/net8.0/win-x64/publish'
artifact: win-x64-tests
displayName: Publish win-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- - publish: '$(testsFolder)/net6.0/linux-x64/publish'
+ - publish: '$(testsFolder)/net8.0/linux-x64/publish'
artifact: linux-x64-tests
displayName: Publish linux-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- - publish: '$(testsFolder)/net6.0/linux-x86/publish'
+ - publish: '$(testsFolder)/net8.0/linux-x86/publish'
artifact: linux-x86-tests
displayName: Publish linux-x86 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- - publish: '$(testsFolder)/net6.0/linux-musl-x64/publish'
+ - publish: '$(testsFolder)/net8.0/linux-musl-x64/publish'
artifact: linux-musl-x64-tests
displayName: Publish linux-musl-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- - publish: '$(testsFolder)/net6.0/freebsd-x64/publish'
+ - publish: '$(testsFolder)/net8.0/freebsd-x64/publish'
artifact: freebsd-x64-tests
displayName: Publish freebsd-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
- - publish: '$(testsFolder)/net6.0/osx-x64/publish'
+ - publish: '$(testsFolder)/net8.0/osx-x64/publish'
artifact: osx-x64-tests
displayName: Publish osx-x64 Test Package
condition: and(succeeded(), eq(variables['osName'], 'Windows'))
@@ -259,21 +259,21 @@ stages:
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).windows-core-x64.zip'
archiveType: 'zip'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/win-x64/net6.0
+ rootFolderOrFile: $(artifactsFolder)/win-x64/net8.0
- task: ArchiveFiles@2
displayName: Create win-x86 zip
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).windows-core-x86.zip'
archiveType: 'zip'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/win-x86/net6.0
+ rootFolderOrFile: $(artifactsFolder)/win-x86/net8.0
- task: ArchiveFiles@2
displayName: Create osx-x64 app
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-app-core-x64.zip'
archiveType: 'zip'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net6.0
+ rootFolderOrFile: $(artifactsFolder)/osx-x64-app/net8.0
- task: ArchiveFiles@2
displayName: Create osx-x64 tar
inputs:
@@ -281,14 +281,14 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/osx-x64/net6.0
+ rootFolderOrFile: $(artifactsFolder)/osx-x64/net8.0
- task: ArchiveFiles@2
displayName: Create osx-arm64 app
inputs:
archiveFile: '$(Build.ArtifactStagingDirectory)/Readarr.$(buildName).osx-app-core-arm64.zip'
archiveType: 'zip'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net6.0
+ rootFolderOrFile: $(artifactsFolder)/osx-arm64-app/net8.0
- task: ArchiveFiles@2
displayName: Create osx-arm64 tar
inputs:
@@ -296,7 +296,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/osx-arm64/net6.0
+ rootFolderOrFile: $(artifactsFolder)/osx-arm64/net8.0
- task: ArchiveFiles@2
displayName: Create linux-x64 tar
inputs:
@@ -304,7 +304,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/linux-x64/net6.0
+ rootFolderOrFile: $(artifactsFolder)/linux-x64/net8.0
- task: ArchiveFiles@2
displayName: Create linux-musl-x64 tar
inputs:
@@ -312,7 +312,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net6.0
+ rootFolderOrFile: $(artifactsFolder)/linux-musl-x64/net8.0
- task: ArchiveFiles@2
displayName: Create linux-x86 tar
inputs:
@@ -320,7 +320,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/linux-x86/net6.0
+ rootFolderOrFile: $(artifactsFolder)/linux-x86/net8.0
- task: ArchiveFiles@2
displayName: Create linux-arm tar
inputs:
@@ -328,7 +328,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/linux-arm/net6.0
+ rootFolderOrFile: $(artifactsFolder)/linux-arm/net8.0
- task: ArchiveFiles@2
displayName: Create linux-musl-arm tar
inputs:
@@ -336,7 +336,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net6.0
+ rootFolderOrFile: $(artifactsFolder)/linux-musl-arm/net8.0
- task: ArchiveFiles@2
displayName: Create linux-arm64 tar
inputs:
@@ -344,7 +344,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/linux-arm64/net6.0
+ rootFolderOrFile: $(artifactsFolder)/linux-arm64/net8.0
- task: ArchiveFiles@2
displayName: Create linux-musl-arm64 tar
inputs:
@@ -352,7 +352,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net6.0
+ rootFolderOrFile: $(artifactsFolder)/linux-musl-arm64/net8.0
- task: ArchiveFiles@2
displayName: Create freebsd-x64 tar
inputs:
@@ -360,7 +360,7 @@ stages:
archiveType: 'tar'
tarCompression: 'gz'
includeRootFolder: false
- rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net6.0
+ rootFolderOrFile: $(artifactsFolder)/freebsd-x64/net8.0
- publish: $(Build.ArtifactStagingDirectory)
artifact: 'Packages'
displayName: Publish Packages
@@ -1205,8 +1205,8 @@ stages:
sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml
sonar.cs.nunit.reportsPaths=$(Build.SourcesDirectory)/TestResult.xml
- bash: |
- ./build.sh --backend -f net6.0 -r win-x64
- TEST_DIR=_tests/net6.0/win-x64/publish/ ./test.sh Windows Unit Coverage
+ ./build.sh --backend -f net8.0 -r win-x64
+ TEST_DIR=_tests/net8.0/win-x64/publish/ ./test.sh Windows Unit Coverage
displayName: Coverage Unit Tests
- task: SonarCloudAnalyze@3
condition: eq(variables['System.PullRequest.IsFork'], 'False')
diff --git a/build.sh b/build.sh
index 6ab41c6bf7..af968f6216 100755
--- a/build.sh
+++ b/build.sh
@@ -79,9 +79,9 @@ Build()
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
- dotnet msbuild -restore $slnFile -p:Configuration=Release -p:Platform=$platform -t:PublishAllRids
+ dotnet msbuild -restore $slnFile -p:SelfContained=True -p:Configuration=Release -p:Platform=$platform -t:PublishAllRids
else
- dotnet msbuild -restore $slnFile -p:Configuration=Release -p:Platform=$platform -p:RuntimeIdentifiers=$RID -t:PublishAllRids
+ dotnet msbuild -restore $slnFile -p:SelfContained=True -p:Configuration=Release -p:Platform=$platform -p:RuntimeIdentifiers=$RID -t:PublishAllRids
fi
ProgressEnd 'Build'
@@ -137,7 +137,7 @@ PackageLinux()
echo "Adding Readarr.Mono to UpdatePackage"
cp $folder/Readarr.Mono.* $folder/Readarr.Update
- if [ "$framework" = "net6.0" ]; then
+ if [ "$framework" = "net8.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Readarr.Update
cp $folder/libMonoPosixHelper.* $folder/Readarr.Update
fi
@@ -165,7 +165,7 @@ PackageMacOS()
echo "Adding Readarr.Mono to UpdatePackage"
cp $folder/Readarr.Mono.* $folder/Readarr.Update
- if [ "$framework" = "net6.0" ]; then
+ if [ "$framework" = "net8.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Readarr.Update
cp $folder/libMonoPosixHelper.* $folder/Readarr.Update
fi
@@ -377,11 +377,11 @@ then
Build
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
- PackageTests "net6.0" "linux-musl-x64"
+ PackageTests "net8.0" "linux-musl-x64"
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
then
- PackageTests "net6.0" "freebsd-x64"
- PackageTests "net6.0" "linux-x86"
+ PackageTests "net8.0" "freebsd-x64"
+ PackageTests "net8.0" "linux-x86"
fi
else
PackageTests "$FRAMEWORK" "$RID"
@@ -409,11 +409,11 @@ then
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
- Package "net6.0" "linux-musl-x64"
+ Package "net8.0" "linux-musl-x64"
if [ "$ENABLE_EXTRA_PLATFORMS" = "YES" ];
then
- Package "net6.0" "freebsd-x64"
- Package "net6.0" "linux-x86"
+ Package "net8.0" "freebsd-x64"
+ Package "net8.0" "linux-x86"
fi
else
Package "$FRAMEWORK" "$RID"
@@ -423,7 +423,7 @@ fi
if [ "$INSTALLER" = "YES" ];
then
InstallInno
- BuildInstaller "net6.0" "win-x64"
- BuildInstaller "net6.0" "win-x86"
+ BuildInstaller "net8.0" "win-x64"
+ BuildInstaller "net8.0" "win-x86"
RemoveInno
fi
diff --git a/deploy/Dockerfile b/deploy/Dockerfile
index 870e6ad3b7..3a5fc5dc9b 100644
--- a/deploy/Dockerfile
+++ b/deploy/Dockerfile
@@ -12,29 +12,40 @@ RUN yarn run build --env production
# → /src/_output/UI
# ---------- Backend ----------
-FROM mcr.microsoft.com/dotnet/sdk:6.0 AS backend
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS backend
WORKDIR /src
COPY .editorconfig ./
COPY src ./src
COPY Logo ./Logo
RUN dotnet msbuild -restore src/Readarr.sln \
+ -p:SelfContained=True \
-p:Configuration=Release \
-p:Platform=Posix \
- -p:RuntimeIdentifiers=linux-musl-x64 \
+ -p:RuntimeIdentifiers=linux-x64 \
-t:PublishAllRids
-# → /src/_output/net6.0/linux-musl-x64/publish
+# → /src/_output/net8.0/linux-x64/publish
# ---------- Runtime ----------
-FROM ghcr.io/linuxserver/baseimage-alpine:3.24
+# Base glibc (Ubuntu) : requise pour le docker mod universal-calibre
+# (conversion de format) qui ne fonctionne pas sur musl/alpine.
+FROM ghcr.io/linuxserver/baseimage-ubuntu:noble
ENV XDG_CONFIG_HOME="/config/xdg" \
COMPlus_EnableDiagnostics=0 \
TMPDIR=/run/readarr-temp
-RUN apk add -U --upgrade --no-cache \
- icu-libs \
- sqlite-libs \
- xmlstarlet
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends \
+ libicu74 \
+ libsqlite3-0 \
+ xmlstarlet \
+ # Bibliothèques natives du Qt embarqué de calibre (mod universal-calibre)
+ libegl1 \
+ libopengl0 \
+ libgl1 \
+ libfontconfig1 \
+ libxkbcommon0 && \
+ rm -rf /var/lib/apt/lists/*
ARG METADATA_URL=http://rreading-glasses:8788
ENV METADATA_URL=${METADATA_URL}
@@ -43,7 +54,7 @@ LABEL maintainer="greenlynxe"
RUN mkdir -p /app/readarr
-COPY --from=backend /src/_output/net6.0/linux-musl-x64/publish /app/readarr/bin
+COPY --from=backend /src/_output/net8.0/linux-x64/publish /app/readarr/bin
COPY --from=frontend /src/_output/UI /app/readarr/bin/UI
RUN echo -e "UpdateMethod=docker\nBranch=main\nPackageVersion=lectarr\nPackageAuthor=greenlynxe" > /app/readarr/package_info
diff --git a/docker-compose.yml b/docker-compose.yml
index 29cb29d3a9..af4114a251 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -24,6 +24,10 @@ services:
# Pour utiliser l'instance publique à la place :
# METADATA_URL: https://api.bookinfo.pro
METADATA_URL: http://rreading-glasses:8788
+ # Décommenter pour activer la conversion automatique de format
+ # (Settings → Media Management → Book Conversion) — installe le CLI
+ # calibre (ebook-convert) au démarrage du conteneur :
+ # DOCKER_MODS: linuxserver/mods:universal-calibre
volumes:
- ./config:/config
# Adapte ces chemins à ton homelab :
diff --git a/docker/Dockerfile b/docker/Dockerfile
index bb47b7c374..c3be6d28d9 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -26,11 +26,11 @@ RUN mkdir -p /app/readarr
RUN --mount=type=bind,source=_output,target=_output \
case "$TARGETPLATFORM" in \
- "linux/amd64") cp -r /_output/net6.0/linux-musl-x64 /app/readarr/bin ;; \
- "linux/arm64") cp -r /_output/net6.0/linux-musl-arm64 /app/readarr/bin ;; \
- "darwin/amd64") cp -r /_output/net6.0/osx-x64 /app/readarr/bin ;; \
- "darwin/arm64") cp -r /_output/net6.0/osx-arm64 /app/readarr/bin ;; \
- "windows/amd64") cp -r /_output/net6.0/win-x64 /app/readarr/bin ;; \
+ "linux/amd64") cp -r /_output/net8.0/linux-musl-x64 /app/readarr/bin ;; \
+ "linux/arm64") cp -r /_output/net8.0/linux-musl-arm64 /app/readarr/bin ;; \
+ "darwin/amd64") cp -r /_output/net8.0/osx-x64 /app/readarr/bin ;; \
+ "darwin/arm64") cp -r /_output/net8.0/osx-arm64 /app/readarr/bin ;; \
+ "windows/amd64") cp -r /_output/net8.0/win-x64 /app/readarr/bin ;; \
*) echo "Unknown platform: $TARGETPLATFORM" && exit 1 ;; \
esac; \
cp -r /_output/UI /app/readarr/bin/UI
diff --git a/docs.sh b/docs.sh
index bd74085058..4966be8a04 100644
--- a/docs.sh
+++ b/docs.sh
@@ -1,7 +1,7 @@
#!/bin/bash
set -e
-FRAMEWORK="net6.0"
+FRAMEWORK="net8.0"
PLATFORM=$1
if [ "$PLATFORM" = "Windows" ]; then
diff --git a/frontend/src/Settings/MediaManagement/MediaManagement.js b/frontend/src/Settings/MediaManagement/MediaManagement.js
index e97a816782..438d525d17 100644
--- a/frontend/src/Settings/MediaManagement/MediaManagement.js
+++ b/frontend/src/Settings/MediaManagement/MediaManagement.js
@@ -39,6 +39,20 @@ const fileDateOptions = [
{ key: 'bookReleaseDate', value: 'Book Release Date' }
];
+const preferredBookFormatOptions = [
+ { key: '', value: 'Disabled' },
+ { key: 'epub', value: 'EPUB' },
+ { key: 'mobi', value: 'MOBI' },
+ { key: 'azw3', value: 'AZW3' },
+ { key: 'pdf', value: 'PDF' }
+];
+
+const contentLanguageDetectionOptions = [
+ { key: 'disabled', value: 'Disabled' },
+ { key: 'logOnly', value: 'Log Only' },
+ { key: 'reject', value: 'Reject on Mismatch' }
+];
+
class MediaManagement extends Component {
//
@@ -234,6 +248,57 @@ class MediaManagement extends Component {
}
+
+