Compare commits

..

5 Commits

Author SHA1 Message Date
Anthony Stirling
ce0f199981 Update add-image.html 2024-08-03 13:07:21 +01:00
Anthony Stirling
4e4a71b56d Update sign.html 2024-08-03 13:07:07 +01:00
Anthony Stirling
5004d2df37 Update add-image.css 2024-08-03 13:06:52 +01:00
Anthony Stirling
d57b36a61a Update sign.css 2024-08-03 13:06:35 +01:00
Anthony Stirling
a35d3223ea Update draggable-utils.js 2024-08-03 13:06:18 +01:00
195 changed files with 1403 additions and 3749 deletions

View File

@@ -11,20 +11,6 @@ body:
This issue form is for reporting bugs only. Please fill out the following sections to help us understand the issue you are facing.
- type: dropdown
id: installation-method
attributes:
label: Installation Method
description: |
Indicate whether you are using Docker or a local installation.
options:
- Docker
- Docker ultra lite
- Docker fat
- Local Installation
validations:
required: true
- type: textarea
id: problem
validations:

View File

@@ -1,8 +1,6 @@
name: Feature Request
description: Submit a new feature request.
title: "[Feature Request]: "
labels:
- enhancement
body:
- type: markdown
attributes:

View File

@@ -9,8 +9,6 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
open-pull-requests-limit: 10
rebase-strategy: "auto"
- package-ecosystem: "docker"
directory: "/" # Location of Dockerfile
schedule:

View File

@@ -2,7 +2,6 @@ Translation:
- changed-files:
- any-glob-to-any-file: 'src/main/resources/messages_*_*.properties'
- any-glob-to-any-file: 'scripts/ignore_translation.toml'
- any-glob-to-any-file: 'src/main/resources/templates/fragments/languages.html'
Front End:
- changed-files:
@@ -47,8 +46,4 @@ Docker:
Test:
- changed-files:
- any-glob-to-any-file: 'cucumber/**/*'
- any-glob-to-any-file: 'src/test**/*'
Github:
- changed-files:
- any-glob-to-any-file: '.github/**/*'
- any-glob-to-any-file: 'test*'

View File

@@ -10,3 +10,9 @@ Closes #(issue_number)
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] My changes generate no new warnings
## Contributor License Agreement
By submitting this pull request, I acknowledge and agree that my contributions will be included in Stirling-PDF and that they can be relicensed in the future under the MPL 2.0 (Mozilla Public License Version 2.0) license.
(This does not change the general open-source nature of Stirling-PDF, simply moving from one license to another license)

32
.github/release.yml vendored
View File

@@ -1,32 +0,0 @@
changelog:
exclude:
labels:
- Documentation
- Test
- Github
categories:
- title: Bug Fixes
labels:
- Bug
- title: Enhancements
labels:
- enhancement
- title: Minor Enhancements
labels:
- Java
- Front End
- title: Docker Updates
labels:
- Docker
- title: Translation Changes
labels:
- Translation
- title: Other Changes
labels:
- "*"

View File

@@ -11,9 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Apply Labels
uses: actions/labeler@v5
- uses: actions/labeler@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
configuration-path: .github/labeler-config.yml

View File

@@ -1,4 +1,4 @@
name: Build repo
name: "Build repo"
on:
push:
@@ -17,72 +17,20 @@ jobs:
strategy:
fail-fast: false
matrix:
jdk-version: [17, 21]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.jdk-version }}
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.jdk-version }}
java-version: "17"
distribution: "temurin"
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@v3
with:
gradle-version: 8.7
- name: Build with Gradle
run: ./gradlew build --no-build-cache
docker-compose-tests:
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
# (github.event_name == 'pull_request' &&
# contains(github.event.pull_request.labels.*.name, 'licenses') == false &&
# (
# contains(github.event.pull_request.labels.*.name, 'Front End') ||
# contains(github.event.pull_request.labels.*.name, 'Java') ||
# contains(github.event.pull_request.labels.*.name, 'Back End') ||
# contains(github.event.pull_request.labels.*.name, 'Security') ||
# contains(github.event.pull_request.labels.*.name, 'API') ||
# contains(github.event.pull_request.labels.*.name, 'Docker') ||
# contains(github.event.pull_request.labels.*.name, 'Test')
# )
# )
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Java 17
uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "adopt"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Install Docker Compose
run: |
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.29.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.7"
- name: Pip requirements
run: |
pip install -r ./cucumber/requirements.txt
- name: Run Docker Compose Tests
run: |
chmod +x ./test.sh
./test.sh

View File

@@ -25,7 +25,7 @@ jobs:
java-version: "17"
distribution: "adopt"
- uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@v3
- name: Run Gradle Command
run: ./gradlew clean generateLicenseReport
@@ -45,7 +45,6 @@ jobs:
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
- name: Create Pull Request
id: cpr
if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@v6
with:
@@ -58,22 +57,6 @@ jobs:
title: "Update 3rd Party Licenses"
body: |
Auto-generated by [create-pull-request][1]
[1]: https://github.com/peter-evans/create-pull-request
labels: licenses
draft: false
delete-branch: true
- name: Auto approve
if: steps.cpr.outputs.pull-request-operation == 'created'
run: gh pr review --approve "${{ steps.cpr.outputs.pull-request-number }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Enable auto-merge
if: steps.cpr.outputs.pull-request-operation == 'created'
uses: peter-evans/enable-pull-request-automerge@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
merge-method: squash # Choose the merge method: merge, squash, or rebase

View File

@@ -22,7 +22,7 @@ jobs:
java-version: "17"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@v3
with:
gradle-version: 8.7
@@ -72,7 +72,7 @@ jobs:
type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }}
- name: Build and push main Dockerfile
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
@@ -98,7 +98,7 @@ jobs:
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
- name: Build and push Dockerfile-ultra-lite
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
if: github.ref != 'refs/heads/main'
with:
context: .
@@ -111,6 +111,7 @@ jobs:
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64,linux/arm64/v8
- name: Generate tags fat
id: meta3
uses: docker/metadata-action@v5
@@ -124,7 +125,7 @@ jobs:
type=raw,value=latest-fat,enable=${{ github.ref == 'refs/heads/master' }}
- name: Build and push main Dockerfile fat
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
if: github.ref != 'refs/heads/main'
with:
builder: ${{ steps.buildx.outputs.name }}

View File

@@ -27,7 +27,7 @@ jobs:
java-version: "17"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@v3
with:
gradle-version: 8.7

View File

@@ -18,7 +18,7 @@ jobs:
java-version: "17"
distribution: "temurin"
- uses: gradle/actions/setup-gradle@v4
- uses: gradle/actions/setup-gradle@v3
- name: Generate Swagger documentation
run: ./gradlew generateOpenApiDocs

47
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: Docker Compose Tests
on:
pull_request:
paths:
- "src/**"
- "**.gradle"
- "!src/main/java/resources/messages*"
- "exampleYmlFiles/**"
- "Dockerfile"
- "Dockerfile**"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Java 17
uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "adopt"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Install Docker Compose
run: |
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.29.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.7"
- name: Pip requirements
run: |
pip install -r ./cucumber/requirements.txt
- name: Run Docker Compose Tests
run: |
chmod +x ./test.sh
./test.sh

View File

@@ -1,5 +1,5 @@
# Main stage
FROM alpine:3.20.2
FROM alpine:3.20.0
# Copy necessary files
COPY scripts /scripts
@@ -39,16 +39,16 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
libreoffice \
# pdftohtml
poppler-utils \
# OCR MY PDF (unpaper for descew and other advanced features)
# OCR MY PDF (unpaper for descew and other advanced featues)
ocrmypdf \
tesseract-ocr-data-eng \
# CV
py3-opencv \
# python3/pip
python3 \
py3-pip && \
python3 && \
wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
# uno unoconv and HTML
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \
mv /usr/share/tessdata /usr/share/tessdata-original && \
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
fc-cache -f -v && \

View File

@@ -12,7 +12,7 @@ RUN DOCKER_ENABLE_SECURITY=true \
./gradlew clean build
# Main stage
FROM alpine:3.20.2
FROM alpine:3.20.0
# Copy necessary files
COPY scripts /scripts
@@ -61,10 +61,10 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
# CV
py3-opencv \
# python3/pip
python3 \
py3-pip && \
python3 && \
wget https://bootstrap.pypa.io/get-pip.py -qO - | python3 - --break-system-packages --no-cache-dir --upgrade && \
# uno unoconv and HTML
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint && \
mv /usr/share/tessdata /usr/share/tessdata-original && \
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
fc-cache -f -v && \

View File

@@ -1,5 +1,5 @@
# use alpine
FROM alpine:3.20.2
FROM alpine:3.20.0
ARG VERSION_TAG

View File

@@ -15,7 +15,7 @@
| file-to-pdf | | ✔️ | | | ✔️ | | | ✔️ | | | |
| img-to-pdf | | ✔️ | | | | | | | | ✔️ | |
| pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | |
| pdf-to-img | | ✔️ | | | | ✔️ | | | | ✔️ | |
| pdf-to-img | | ✔️ | | | | | | | | ✔️ | |
| pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | |
| pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | |
| pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | |

View File

@@ -169,42 +169,42 @@ Stirling PDF currently supports 38!
| Language | Progress |
| ------------------------------------------- | -------------------------------------- |
| Arabic (العربية) (ar_AR) | ![44%](https://geps.dev/progress/44) |
| Basque (Euskara) (eu_ES) | ![60%](https://geps.dev/progress/60) |
| Bulgarian (Български) (bg_BG) | ![92%](https://geps.dev/progress/92) |
| Catalan (Català) (ca_CA) | ![47%](https://geps.dev/progress/47) |
| Croatian (Hrvatski) (hr_HR) | ![92%](https://geps.dev/progress/92) |
| Czech (Česky) (cs_CZ) | ![88%](https://geps.dev/progress/88) |
| Danish (Dansk) (da_DK) | ![9%](https://geps.dev/progress/9) |
| Dutch (Nederlands) (nl_NL) | ![94%](https://geps.dev/progress/94) |
| Arabic (العربية) (ar_AR) | ![45%](https://geps.dev/progress/45) |
| Basque (Euskara) (eu_ES) | ![62%](https://geps.dev/progress/62) |
| Bulgarian (Български) (bg_BG) | ![94%](https://geps.dev/progress/94) |
| Catalan (Català) (ca_CA) | ![48%](https://geps.dev/progress/48) |
| Croatian (Hrvatski) (hr_HR) | ![95%](https://geps.dev/progress/95) |
| Czech (Česky) (cs_CZ) | ![90%](https://geps.dev/progress/90) |
| Danish (Dansk) (da_DK) | ![10%](https://geps.dev/progress/10) |
| Dutch (Nederlands) (nl_NL) | ![96%](https://geps.dev/progress/96) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![91%](https://geps.dev/progress/91) |
| German (Deutsch) (de_DE) | ![98%](https://geps.dev/progress/98) |
| Greek (Ελληνικά) (el_GR) | ![80%](https://geps.dev/progress/80) |
| Hindi (हिंदी) (hi_IN) | ![75%](https://geps.dev/progress/75) |
| Hungarian (Magyar) (hu_HU) | ![74%](https://geps.dev/progress/74) |
| Indonesia (Bahasa Indonesia) (id_ID) | ![74%](https://geps.dev/progress/74) |
| Irish (Gaeilge) (ga_IE) | ![96%](https://geps.dev/progress/96) |
| French (Français) (fr_FR) | ![94%](https://geps.dev/progress/94) |
| German (Deutsch) (de_DE) | ![100%](https://geps.dev/progress/100) |
| Greek (Ελληνικά) (el_GR) | ![82%](https://geps.dev/progress/82) |
| Hindi (हिंदी) (hi_IN) | ![77%](https://geps.dev/progress/77) |
| Hungarian (Magyar) (hu_HU) | ![76%](https://geps.dev/progress/76) |
| Indonesia (Bahasa Indonesia) (id_ID) | ![76%](https://geps.dev/progress/76) |
| Irish (Gaeilge) (ga_IE) | ![99%](https://geps.dev/progress/99) |
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
| Japanese (日本語) (ja_JP) | ![90%](https://geps.dev/progress/90) |
| Korean (한국어) (ko_KR) | ![82%](https://geps.dev/progress/82) |
| Korean (한국어) (ko_KR) | ![84%](https://geps.dev/progress/84) |
| Norwegian (Norsk) (no_NB) | ![96%](https://geps.dev/progress/96) |
| Polish (Polski) (pl_PL) | ![90%](https://geps.dev/progress/90) |
| Portuguese (Português) (pt_PT) | ![76%](https://geps.dev/progress/76) |
| Portuguese Brazilian (Português) (pt_BR) | ![99%](https://geps.dev/progress/99) |
| Romanian (Română) (ro_RO) | ![38%](https://geps.dev/progress/38) |
| Russian (Русский) (ru_RU) | ![82%](https://geps.dev/progress/82) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![76%](https://geps.dev/progress/76) |
| Simplified Chinese (简体中文) (zh_CN) | ![97%](https://geps.dev/progress/97) |
| Slovakian (Slovensky) (sk_SK) | ![90%](https://geps.dev/progress/90) |
| Spanish (Español) (es_ES) | ![96%](https://geps.dev/progress/96) |
| Swedish (Svenska) (sv_SE) | ![38%](https://geps.dev/progress/38) |
| Thai (ไทย) (th_TH) | ![97%](https://geps.dev/progress/97) |
| Traditional Chinese (繁體中文) (zh_TW) | ![96%](https://geps.dev/progress/96) |
| Turkish (Türkçe) (tr_TR) | ![97%](https://geps.dev/progress/97) |
| Ukrainian (Українська) (uk_UA) | ![88%](https://geps.dev/progress/88) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![97%](https://geps.dev/progress/97) |
| Portuguese (Português) (pt_PT) | ![78%](https://geps.dev/progress/78) |
| Portuguese Brazilian (Português) (pt_BR) | ![60%](https://geps.dev/progress/60) |
| Romanian (Română) (ro_RO) | ![39%](https://geps.dev/progress/39) |
| Russian (Русский) (ru_RU) | ![84%](https://geps.dev/progress/84) |
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) | ![78%](https://geps.dev/progress/78) |
| Simplified Chinese (简体中文) (zh_CN) | ![99%](https://geps.dev/progress/99) |
| Slovakian (Slovensky) (sk_SK) | ![92%](https://geps.dev/progress/92) |
| Spanish (Español) (es_ES) | ![98%](https://geps.dev/progress/98) |
| Swedish (Svenska) (sv_SE) | ![39%](https://geps.dev/progress/39) |
| Thai (ไทย) (th_TH) | ![100%](https://geps.dev/progress/100) |
| Traditional Chinese (繁體中文) (zh_TW) | ![98%](https://geps.dev/progress/98) |
| Turkish (Türkçe) (tr_TR) | ![99%](https://geps.dev/progress/99) |
| Ukrainian (Українська) (uk_UA) | ![90%](https://geps.dev/progress/90) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![99%](https://geps.dev/progress/99) |
## Contributing (creating issues, translations, fixing bugs, etc.)
@@ -266,7 +266,6 @@ security:
clientId: '' # Client ID from your provider
clientSecret: '' # Client Secret from your provider
autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
useAsUsername: email # Default is 'email'; custom fields can be used as the username
scopes: openid, profile, email # Specify the scopes for which the application will request permissions
provider: google # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'

View File

@@ -1,33 +1,25 @@
plugins {
id "java"
id "org.springframework.boot" version "3.3.2"
id "org.springframework.boot" version "3.3.0"
id "io.spring.dependency-management" version "1.1.6"
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
id "io.swagger.swaggerhub" version "1.3.2"
id "edu.sc.seis.launch4j" version "3.0.6"
id "edu.sc.seis.launch4j" version "3.0.5"
id "com.diffplug.spotless" version "6.25.0"
id "com.github.jk1.dependency-license-report" version "2.9"
//id "nebula.lint" version "19.0.3"
id "com.github.jk1.dependency-license-report" version "2.8"
}
import com.github.jk1.license.render.*
ext {
springBootVersion = "3.3.2"
pdfboxVersion = "3.0.3"
logbackVersion = "1.5.7"
imageioVersion = "3.11.0"
lombokVersion = "1.18.34"
bouncycastleVersion = "1.78.1"
}
group = "stirling.software"
version = "0.28.2"
version = "0.26.2"
java {
// 17 is lowest but we support and recommend 21
sourceCompatibility = JavaVersion.VERSION_17
}
// 17 is lowest but we support and recommend 21
sourceCompatibility = "17"
repositories {
mavenCentral()
@@ -51,7 +43,6 @@ sourceSets {
exclude "stirling/software/SPDF/model/AttemptCounter.java"
exclude "stirling/software/SPDF/model/Authority.java"
exclude "stirling/software/SPDF/model/PersistentLogin.java"
exclude "stirling/software/SPDF/model/SessionEntity.java"
exclude "stirling/software/SPDF/model/User.java"
exclude "stirling/software/SPDF/repository/**"
}
@@ -101,68 +92,66 @@ spotless {
}
}
//gradleLint {
// rules=['unused-dependency']
// }
tasks.wrapper {
gradleVersion = "8.7"
}
//tasks.withType(JavaCompile) {
// options.compilerArgs << "-Xlint:deprecation"
//}
configurations.all {
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
}
dependencies {
//security updates
implementation "ch.qos.logback:logback-classic:1.5.6"
implementation "ch.qos.logback:logback-core:1.5.6"
implementation "org.springframework:spring-webmvc:6.1.9"
implementation("io.github.pixee:java-security-toolkit:1.2.0")
implementation("io.github.pixee:java-security-toolkit:1.1.3")
// implementation "org.yaml:snakeyaml:2.2"
implementation 'com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4'
// Exclude Tomcat and include Jetty
implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion") {
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
}
implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
runtimeOnly "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE"
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE"
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
//2.2.x requires rebuild of DB file.. need migration path
runtimeOnly "com.h2database:h2:2.1.214"
implementation "com.h2database:h2:2.1.214"
// implementation "com.h2database:h2:2.2.224"
}
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
// Batik
implementation "org.apache.xmlgraphics:batik-all:1.17"
// TwelveMonkeys
runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion"
runtimeOnly "com.twelvemonkeys.imageio:imageio-bmp:$imageioVersion"
// implementation "com.twelvemonkeys.imageio:imageio-hdr:$imageioVersion"
// implementation "com.twelvemonkeys.imageio:imageio-icns:$imageioVersion"
// implementation "com.twelvemonkeys.imageio:imageio-iff:$imageioVersion"
runtimeOnly "com.twelvemonkeys.imageio:imageio-jpeg:$imageioVersion"
// implementation "com.twelvemonkeys.imageio:imageio-pcx:$imageioVersion@
// implementation "com.twelvemonkeys.imageio:imageio-pict:$imageioVersion"
// implementation "com.twelvemonkeys.imageio:imageio-pnm:$imageioVersion"
// implementation "com.twelvemonkeys.imageio:imageio-psd:$imageioVersion"
// implementation "com.twelvemonkeys.imageio:imageio-sgi:$imageioVersion"
// implementation "com.twelvemonkeys.imageio:imageio-tga:$imageioVersion"
// implementation "com.twelvemonkeys.imageio:imageio-thumbsdb:$imageioVersion"
runtimeOnly "com.twelvemonkeys.imageio:imageio-tiff:$imageioVersion"
runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion"
// implementation "com.twelvemonkeys.imageio:imageio-xwd:$imageioVersion"
implementation "com.twelvemonkeys.imageio:imageio-batik:3.10.1"
implementation "com.twelvemonkeys.imageio:imageio-bmp:3.10.1"
// implementation "com.twelvemonkeys.imageio:imageio-hdr:3.10.1"
// implementation "com.twelvemonkeys.imageio:imageio-icns:3.10.1"
// implementation "com.twelvemonkeys.imageio:imageio-iff:3.10.1"
implementation "com.twelvemonkeys.imageio:imageio-jpeg:3.11.0"
// implementation "com.twelvemonkeys.imageio:imageio-pcx:3.10.1"
// implementation "com.twelvemonkeys.imageio:imageio-pict:3.10.1"
// implementation "com.twelvemonkeys.imageio:imageio-pnm:3.10.1"
// implementation "com.twelvemonkeys.imageio:imageio-psd:3.10.1"
// implementation "com.twelvemonkeys.imageio:imageio-sgi:3.10.1"
// implementation "com.twelvemonkeys.imageio:imageio-tga:3.10.1"
// implementation "com.twelvemonkeys.imageio:imageio-thumbsdb:3.10.1"
implementation "com.twelvemonkeys.imageio:imageio-tiff:3.10.1"
implementation "com.twelvemonkeys.imageio:imageio-webp:3.10.1"
// implementation "com.twelvemonkeys.imageio:imageio-xwd:3.10.1"
implementation "commons-io:commons-io:2.16.1"
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0"
//general PDF
// https://mvnrepository.com/artifact/com.opencsv/opencsv
@@ -170,36 +159,33 @@ dependencies {
exclude group: "commons-logging", module: "commons-logging"
}
implementation ("org.apache.pdfbox:pdfbox:$pdfboxVersion") {
implementation ("org.apache.pdfbox:pdfbox:3.0.2") {
exclude group: "commons-logging", module: "commons-logging"
}
implementation ("org.apache.pdfbox:xmpbox:$pdfboxVersion") {
implementation ("org.apache.pdfbox:xmpbox:3.0.2") {
exclude group: "commons-logging", module: "commons-logging"
}
implementation 'org.apache.pdfbox:jbig2-imageio:3.0.4'
implementation "com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4"
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcprov-jdk18on:1.78.1"
implementation "org.bouncycastle:bcpkix-jdk18on:1.78.1"
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
implementation "io.micrometer:micrometer-core:1.13.3"
implementation "io.micrometer:micrometer-core:1.13.0"
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
// https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark:0.22.0"
implementation "org.commonmark:commonmark-ext-gfm-tables:0.22.0"
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
implementation "com.fathzer:javaluator:3.0.4"
implementation "com.bucket4j:bucket4j_jdk17-core:8.12.1"
implementation "com.fathzer:javaluator:3.0.4"
developmentOnly("org.springframework.boot:spring-boot-devtools:$springBootVersion")
compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
compileOnly "org.projectlombok:lombok:1.18.32"
annotationProcessor "org.projectlombok:lombok:1.18.32"
testRuntimeOnly 'org.mockito:mockito-inline:5.2.0'
testImplementation 'org.mockito:mockito-inline:3.12.4'
}
tasks.withType(JavaCompile).configureEach {
@@ -210,7 +196,7 @@ compileJava {
options.compilerArgs << "-parameters"
}
task writeVersion {
task writeVersion {
def propsFile = file("src/main/resources/version.properties")
def props = new Properties()
props.setProperty("version", version)
@@ -240,6 +226,6 @@ tasks.named("test") {
useJUnitPlatform()
}
task printVersion {
task printVersion {
println project.version
}

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 0.28.2
appVersion: 0.26.2
description: locally hosted web application that allows you to perform various operations
on PDF files
home: https://github.com/Stirling-Tools/Stirling-PDF

View File

@@ -94,7 +94,7 @@ Feature: API Validation
When I send the API request to the endpoint "/api/v1/misc/remove-blanks"
Then the response content type should be "application/octet-stream"
And the response file should have extension ".zip"
And the response ZIP should contain 1 files
And the response ZIP should contain 2 files
And the response file should have size greater than 0
@positive @flatten

View File

@@ -181,9 +181,7 @@ ignore = [
[pt_BR]
ignore = [
'changeMetadata.trapped',
'language.direction',
'pipelineOptions.pipelineHeader',
]
[pt_PT]

View File

@@ -1,174 +0,0 @@
"""
Author: Ludy87
Description: This script converts a PDF file to WebP images. It includes functionality to resize images if they exceed specified dimensions and handle conversion of PDF pages to WebP format.
Example
-------
To convert a PDF file to WebP images with each page as a separate WebP file:
python script.py input.pdf output_directory
To convert a PDF file to a single WebP image:
python script.py input.pdf output_directory --single
To adjust the DPI resolution for rendering PDF pages:
python script.py input.pdf output_directory --dpi 150
"""
import argparse
import os
from pdf2image import convert_from_path
from PIL import Image
def resize_image(input_image_path, output_image_path, max_size=(16383, 16383)):
"""
Resize the image if its dimensions exceed the maximum allowed size and save it as WebP.
Parameters
----------
input_image_path : str
Path to the input image file.
output_image_path : str
Path where the output WebP image will be saved.
max_size : tuple of int, optional
Maximum allowed dimensions for the image (width, height). Default is (16383, 16383).
Returns
-------
None
"""
try:
# Open the image
image = Image.open(input_image_path)
width, height = image.size
max_width, max_height = max_size
# Check if the image dimensions exceed the maximum allowed dimensions
if width > max_width or height > max_height:
# Calculate the scaling ratio
ratio = min(max_width / width, max_height / height)
new_width = int(width * ratio)
new_height = int(height * ratio)
# Resize the image
resized_image = image.resize((new_width, new_height), Image.LANCZOS)
resized_image.save(output_image_path, format="WEBP", quality=100)
print(
f"The image was successfully resized to ({new_width}, {new_height}) and saved as WebP: {output_image_path}"
)
else:
# If dimensions are within the allowed limits, save the image directly
image.save(output_image_path, format="WEBP", quality=100)
print(f"The image was successfully saved as WebP: {output_image_path}")
except Exception as e:
print(f"An error occurred: {e}")
def convert_image_to_webp(input_image, output_file):
"""
Convert an image to WebP format, resizing it if it exceeds the maximum dimensions.
Parameters
----------
input_image : str
Path to the input image file.
output_file : str
Path where the output WebP image will be saved.
Returns
-------
None
"""
# Resize the image if it exceeds the maximum dimensions
resize_image(input_image, output_file, max_size=(16383, 16383))
def pdf_to_webp(pdf_path, output_dir, dpi=300):
"""
Convert each page of a PDF file to WebP images.
Parameters
----------
pdf_path : str
Path to the input PDF file.
output_dir : str
Directory where the WebP images will be saved.
dpi : int, optional
DPI resolution for rendering PDF pages. Default is 300.
Returns
-------
None
"""
# Convert the PDF to a list of images
images = convert_from_path(pdf_path, dpi=dpi)
for page_number, image in enumerate(images):
# Define temporary PNG path
temp_png_path = os.path.join(output_dir, f"temp_page_{page_number + 1}.png")
image.save(temp_png_path, format="PNG")
# Define the output path for WebP
output_path = os.path.join(output_dir, f"page_{page_number + 1}.webp")
# Convert PNG to WebP
convert_image_to_webp(temp_png_path, output_path)
# Delete the temporary PNG file
os.remove(temp_png_path)
def main(pdf_image_path, output_dir, dpi=300, single_images_flag=False):
"""
Main function to handle conversion from PDF to WebP images.
Parameters
----------
pdf_image_path : str
Path to the input PDF file or image.
output_dir : str
Directory where the WebP images will be saved.
dpi : int, optional
DPI resolution for rendering PDF pages. Default is 300.
single_images_flag : bool, optional
If True, combine all pages into a single WebP image. Default is False.
Returns
-------
None
"""
if single_images_flag:
# Combine all pages into a single WebP image
output_path = os.path.join(output_dir, "combined_image.webp")
convert_image_to_webp(pdf_image_path, output_path)
else:
# Convert each PDF page to a separate WebP image
pdf_to_webp(pdf_image_path, output_dir, dpi)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Convert a PDF file to WebP images.")
parser.add_argument("pdf_path", help="The path to the input PDF file.")
parser.add_argument(
"output_dir", help="The directory where the WebP images should be saved."
)
parser.add_argument(
"--dpi",
type=int,
default=300,
help="The DPI resolution for rendering the PDF pages (default: 300).",
)
parser.add_argument(
"--single",
action="store_true",
help="Combine all pages into a single WebP image.",
)
args = parser.parse_args()
os.makedirs(args.output_dir, exist_ok=True)
main(
args.pdf_path,
args.output_dir,
dpi=args.dpi,
single_images_flag=args.single,
)

View File

@@ -65,7 +65,6 @@ public class SPdfApplication {
public static void main(String[] args) throws IOException, InterruptedException {
SpringApplication app = new SpringApplication(SPdfApplication.class);
app.setAdditionalProfiles("default");
app.addInitializers(new ConfigInitializer());
Map<String, String> propertyFiles = new HashMap<>();
@@ -79,8 +78,7 @@ public class SPdfApplication {
// custom javs settings file
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
String existingLocation =
propertyFiles.getOrDefault("spring.config.additional-location", "");
String existingLocation = propertyFiles.getOrDefault("spring.config.additional-location", "");
if (!existingLocation.isEmpty()) {
existingLocation += ",";
}

View File

@@ -14,8 +14,6 @@ import java.util.List;
import org.simpleyaml.configuration.comments.CommentType;
import org.simpleyaml.configuration.file.YamlFile;
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContextInitializer;
@@ -73,17 +71,9 @@ public class ConfigInitializer
}
final YamlFile settingsTemplateFile = new YamlFile(tempTemplatePath.toFile());
DumperOptions yamlOptionsSettingsTemplateFile =
((SimpleYamlImplementation) settingsTemplateFile.getImplementation())
.getDumperOptions();
yamlOptionsSettingsTemplateFile.setSplitLines(false);
settingsTemplateFile.loadWithComments();
final YamlFile settingsFile = new YamlFile(settingsPath.toFile());
DumperOptions yamlOptionsSettingsFile =
((SimpleYamlImplementation) settingsFile.getImplementation())
.getDumperOptions();
yamlOptionsSettingsFile.setSplitLines(false);
settingsFile.loadWithComments();
// Load headers and comments
@@ -91,10 +81,6 @@ public class ConfigInitializer
// Create a new file for temporary settings
final YamlFile tempSettingFile = new YamlFile(settingsPath.toFile());
DumperOptions yamlOptionsTempSettingFile =
((SimpleYamlImplementation) tempSettingFile.getImplementation())
.getDumperOptions();
yamlOptionsTempSettingFile.setSplitLines(false);
tempSettingFile.createNewFile(true);
tempSettingFile.setHeader(header);

View File

@@ -137,7 +137,6 @@ public class EndpointConfiguration {
addEndpointToGroup("Other", "auto-rename");
addEndpointToGroup("Other", "get-info-on-pdf");
addEndpointToGroup("Other", "show-javascript");
addEndpointToGroup("Other", "remove-image-pdf");
// CLI
addEndpointToGroup("CLI", "compress-pdf");
@@ -166,7 +165,6 @@ public class EndpointConfiguration {
addEndpointToGroup("Python", REMOVE_BLANKS);
addEndpointToGroup("Python", "html-to-pdf");
addEndpointToGroup("Python", "url-to-pdf");
addEndpointToGroup("Python", "pdf-to-img");
// openCV
addEndpointToGroup("OpenCV", "extract-image-scans");
@@ -223,7 +221,6 @@ public class EndpointConfiguration {
addEndpointToGroup("Java", "split-pdf-by-sections");
addEndpointToGroup("Java", REMOVE_BLANKS);
addEndpointToGroup("Java", "pdf-to-text");
addEndpointToGroup("Java", "remove-image-pdf");
// Javascript
addEndpointToGroup("Javascript", "pdf-organizer");

View File

@@ -3,8 +3,9 @@ package stirling.software.SPDF.config.security;
import java.io.IOException;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
@@ -14,16 +15,17 @@ import org.springframework.security.web.authentication.SimpleUrlAuthenticationFa
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.User;
@Slf4j
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private LoginAttemptService loginAttemptService;
private UserService userService;
private static final Logger logger =
LoggerFactory.getLogger(CustomAuthenticationFailureHandler.class);
public CustomAuthenticationFailureHandler(
final LoginAttemptService loginAttemptService, UserService userService) {
this.loginAttemptService = loginAttemptService;
@@ -37,17 +39,14 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF
AuthenticationException exception)
throws IOException, ServletException {
if (exception instanceof DisabledException) {
log.error("User is deactivated: ", exception);
getRedirectStrategy().sendRedirect(request, response, "/logout?userIsDisabled=true");
return;
}
String ip = request.getRemoteAddr();
log.error("Failed login attempt from IP: {}", ip);
logger.error("Failed login attempt from IP: {}", ip);
if (exception instanceof LockedException) {
getRedirectStrategy().sendRedirect(request, response, "/login?error=locked");
String contextPath = request.getContextPath();
if (exception.getClass().isAssignableFrom(InternalAuthenticationServiceException.class)
|| "Password must not be null".equalsIgnoreCase(exception.getMessage())) {
response.sendRedirect(contextPath + "/login?error=oauth2AuthenticationError");
return;
}
@@ -55,25 +54,20 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF
Optional<User> optUser = userService.findByUsernameIgnoreCase(username);
if (username != null && optUser.isPresent() && !isDemoUser(optUser)) {
log.info(
logger.info(
"Remaining attempts for user {}: {}",
username,
optUser.get().getUsername(),
loginAttemptService.getRemainingAttempts(username));
loginAttemptService.loginFailed(username);
if (loginAttemptService.isBlocked(username) || exception instanceof LockedException) {
getRedirectStrategy().sendRedirect(request, response, "/login?error=locked");
if (loginAttemptService.isBlocked(username)
|| exception.getClass().isAssignableFrom(LockedException.class)) {
response.sendRedirect(contextPath + "/login?error=locked");
return;
}
}
if (exception instanceof BadCredentialsException
|| exception instanceof UsernameNotFoundException) {
getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials");
return;
}
if (exception instanceof InternalAuthenticationServiceException
|| "Password must not be null".equalsIgnoreCase(exception.getMessage())) {
getRedirectStrategy()
.sendRedirect(request, response, "/login?error=oauth2AuthenticationError");
if (exception.getClass().isAssignableFrom(BadCredentialsException.class)
|| exception.getClass().isAssignableFrom(UsernameNotFoundException.class)) {
response.sendRedirect(contextPath + "/login?error=badcredentials");
return;
}

View File

@@ -10,20 +10,15 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.utils.RequestUriUtils;
@Slf4j
public class CustomAuthenticationSuccessHandler
extends SavedRequestAwareAuthenticationSuccessHandler {
private LoginAttemptService loginAttemptService;
private UserService userService;
public CustomAuthenticationSuccessHandler(
LoginAttemptService loginAttemptService, UserService userService) {
public CustomAuthenticationSuccessHandler(LoginAttemptService loginAttemptService) {
this.loginAttemptService = loginAttemptService;
this.userService = userService;
}
@Override
@@ -32,10 +27,6 @@ public class CustomAuthenticationSuccessHandler
throws ServletException, IOException {
String userName = request.getParameter("username");
if (userService.isUserDisabled(userName)) {
getRedirectStrategy().sendRedirect(request, response, "/logout?userIsDisabled=true");
return;
}
loginAttemptService.loginSucceeded(userName);
// Get the saved request

View File

@@ -2,26 +2,32 @@ package stirling.software.SPDF.config.security;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
@Autowired SessionRegistry sessionRegistry;
@Override
public void onLogoutSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
if (request.getParameter("userIsDisabled") != null) {
getRedirectStrategy()
.sendRedirect(request, response, "/login?erroroauth=userIsDisabled");
return;
HttpSession session = request.getSession(false);
if (session != null) {
String sessionId = session.getId();
sessionRegistry.removeSessionInformation(sessionId);
session.invalidate();
logger.debug("Session invalidated: " + sessionId);
}
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
response.sendRedirect(request.getContextPath() + "/login?logout=true");
}
}

View File

@@ -6,8 +6,6 @@ import java.nio.file.Paths;
import java.util.UUID;
import org.simpleyaml.configuration.file.YamlFile;
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -94,9 +92,6 @@ public class InitialSecuritySetup {
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
final YamlFile settingsYml = new YamlFile(path.toFile());
DumperOptions yamlOptionssettingsYml =
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
yamlOptionssettingsYml.setSplitLines(false);
settingsYml.loadWithComments();

View File

@@ -3,6 +3,8 @@ package stirling.software.SPDF.config.security;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -15,6 +17,8 @@ public class LoginAttemptService {
@Autowired ApplicationProperties applicationProperties;
private static final Logger logger = LoggerFactory.getLogger(LoginAttemptService.class);
private int MAX_ATTEMPT;
private long ATTEMPT_INCREMENT_TIME;
private ConcurrentHashMap<String, AttemptCounter> attemptsCache;

View File

@@ -10,6 +10,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -17,6 +18,8 @@ import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
@@ -34,7 +37,6 @@ import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationF
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
@@ -45,7 +47,7 @@ import stirling.software.SPDF.model.provider.KeycloakProvider;
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
@Configuration
@EnableWebSecurity
@EnableWebSecurity()
@EnableMethodSecurity
public class SecurityConfiguration {
@@ -71,7 +73,11 @@ public class SecurityConfiguration {
@Autowired private LoginAttemptService loginAttemptService;
@Autowired private FirstLoginFilter firstLoginFilter;
@Autowired private SessionPersistentRegistry sessionRegistry;
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
@@ -88,7 +94,7 @@ public class SecurityConfiguration {
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(10)
.maxSessionsPreventsLogin(false)
.sessionRegistry(sessionRegistry)
.sessionRegistry(sessionRegistry())
.expiredUrl("/login?logout=true"));
http.formLogin(
@@ -97,7 +103,7 @@ public class SecurityConfiguration {
.loginPage("/login")
.successHandler(
new CustomAuthenticationSuccessHandler(
loginAttemptService, userService))
loginAttemptService))
.defaultSuccessUrl("/")
.failureHandler(
new CustomAuthenticationFailureHandler(
@@ -149,15 +155,12 @@ public class SecurityConfiguration {
})
.permitAll()
.anyRequest()
.authenticated());
.authenticated())
.authenticationProvider(authenticationProvider());
// Handle OAUTH2 Logins
if (applicationProperties.getSecurity().getOAUTH2() != null
&& applicationProperties.getSecurity().getOAUTH2().getEnabled()
&& !applicationProperties
.getSecurity()
.getLoginMethod()
.equalsIgnoreCase("normal")) {
&& applicationProperties.getSecurity().getOAUTH2().getEnabled()) {
http.oauth2Login(
oauth2 ->
@@ -188,8 +191,10 @@ public class SecurityConfiguration {
.logout(
logout ->
logout.logoutSuccessHandler(
new CustomOAuth2LogoutSuccessHandler(
applicationProperties)));
new CustomOAuth2LogoutSuccessHandler(
this.applicationProperties,
sessionRegistry()))
.invalidateHttpSession(true));
}
} else {
http.csrf(csrf -> csrf.disable())
@@ -377,6 +382,14 @@ public class SecurityConfiguration {
return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public PersistentTokenRepository persistentTokenRepository() {
return new JPATokenRepositoryImpl();

View File

@@ -1,9 +1,6 @@
package stirling.software.SPDF.config.security;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -11,11 +8,9 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@@ -23,16 +18,14 @@ import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
import stirling.software.SPDF.model.User;
@Component
public class UserAuthenticationFilter extends OncePerRequestFilter {
@Autowired @Lazy private UserService userService;
@Autowired private UserDetailsService userDetailsService;
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
@Autowired @Lazy private UserService userService;
@Autowired
@Qualifier("loginEnabled")
@@ -58,20 +51,15 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
try {
// Use API key to authenticate. This requires you to have an authentication
// provider for API keys.
Optional<User> user = userService.getUserByApiKey(apiKey);
if (!user.isPresent()) {
UserDetails userDetails = userService.loadUserByApiKey(apiKey);
if (userDetails == null) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("Invalid API Key.");
return;
}
List<SimpleGrantedAuthority> authorities =
user.get().getAuthorities().stream()
.map(
authority ->
new SimpleGrantedAuthority(
authority.getAuthority()))
.collect(Collectors.toList());
authentication = new ApiKeyAuthenticationToken(user.get(), apiKey, authorities);
authentication =
new ApiKeyAuthenticationToken(
userDetails, apiKey, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (AuthenticationException e) {
// If API key authentication fails, deny the request
@@ -99,43 +87,6 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
}
}
// Check if the authenticated user is disabled and invalidate their session if so
if (authentication != null && authentication.isAuthenticated()) {
Object principal = authentication.getPrincipal();
String username = null;
if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
} else if (principal instanceof OAuth2User) {
username = ((OAuth2User) principal).getName();
} else if (principal instanceof String) {
username = (String) principal;
}
List<SessionInformation> sessionsInformations =
sessionPersistentRegistry.getAllSessions(principal, false);
if (username != null) {
boolean isUserExists = userService.usernameExistsIgnoreCase(username);
boolean isUserDisabled = userService.isUserDisabled(username);
if (!isUserExists || isUserDisabled) {
for (SessionInformation sessionsInformation : sessionsInformations) {
sessionsInformation.expireNow();
sessionPersistentRegistry.expireSession(sessionsInformation.getSessionId());
}
}
if (!isUserExists) {
response.sendRedirect(request.getContextPath() + "/logout?badcredentials=true");
return;
}
if (isUserDisabled) {
response.sendRedirect(request.getContextPath() + "/logout?userIsDisabled=true");
return;
}
}
}
filterChain.doFilter(request, response);
}

View File

@@ -15,15 +15,12 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import stirling.software.SPDF.config.DatabaseBackupInterface;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.model.Authority;
@@ -43,8 +40,6 @@ public class UserService implements UserServiceInterface {
@Autowired private MessageSource messageSource;
@Autowired private SessionPersistentRegistry sessionRegistry;
@Autowired DatabaseBackupInterface databaseBackupHelper;
// Handle OAUTH2 login and user auto creation.
@@ -53,7 +48,7 @@ public class UserService implements UserServiceInterface {
if (!isUsernameValid(username)) {
return false;
}
Optional<User> existingUser = findByUsernameIgnoreCase(username);
Optional<User> existingUser = userRepository.findByUsernameIgnoreCase(username);
if (existingUser.isPresent()) {
return true;
}
@@ -65,8 +60,8 @@ public class UserService implements UserServiceInterface {
}
public Authentication getAuthentication(String apiKey) {
Optional<User> user = getUserByApiKey(apiKey);
if (!user.isPresent()) {
User user = getUserByApiKey(apiKey);
if (user == null) {
throw new UsernameNotFoundException("API key is not valid");
}
@@ -74,7 +69,7 @@ public class UserService implements UserServiceInterface {
return new UsernamePasswordAuthenticationToken(
user, // principal (typically the user)
null, // credentials (we don't expose the password or API key here)
getAuthorities(user.get()) // user's authorities (roles/permissions)
getAuthorities(user) // user's authorities (roles/permissions)
);
}
@@ -89,17 +84,18 @@ public class UserService implements UserServiceInterface {
String apiKey;
do {
apiKey = UUID.randomUUID().toString();
} while (userRepository.findByApiKey(apiKey).isPresent()); // Ensure uniqueness
} while (userRepository.findByApiKey(apiKey) != null); // Ensure uniqueness
return apiKey;
}
public User addApiKeyToUser(String username) {
Optional<User> user = findByUsernameIgnoreCase(username);
if (user.isPresent()) {
user.get().setApiKey(generateApiKey());
return userRepository.save(user.get());
}
throw new UsernameNotFoundException("User not found");
User user =
userRepository
.findByUsernameIgnoreCase(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
user.setApiKey(generateApiKey());
return userRepository.save(user);
}
public User refreshApiKeyForUser(String username) {
@@ -108,40 +104,39 @@ public class UserService implements UserServiceInterface {
public String getApiKeyForUser(String username) {
User user =
findByUsernameIgnoreCase(username)
userRepository
.findByUsernameIgnoreCase(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return user.getApiKey();
}
public boolean isValidApiKey(String apiKey) {
return userRepository.findByApiKey(apiKey).isPresent();
return userRepository.findByApiKey(apiKey) != null;
}
public Optional<User> getUserByApiKey(String apiKey) {
public User getUserByApiKey(String apiKey) {
return userRepository.findByApiKey(apiKey);
}
public Optional<User> loadUserByApiKey(String apiKey) {
Optional<User> user = userRepository.findByApiKey(apiKey);
if (user.isPresent()) {
return user;
public UserDetails loadUserByApiKey(String apiKey) {
User user = userRepository.findByApiKey(apiKey);
if (user != null) {
// Convert your User entity to a UserDetails object with authorities
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(), // you might not need this for API key auth
getAuthorities(user));
}
return null; // or throw an exception
}
public boolean validateApiKeyForUser(String username, String apiKey) {
Optional<User> userOpt = findByUsernameIgnoreCase(username);
Optional<User> userOpt = userRepository.findByUsernameIgnoreCase(username);
return userOpt.isPresent() && apiKey.equals(userOpt.get().getApiKey());
}
public void saveUser(String username, AuthenticationType authenticationType)
throws IllegalArgumentException, IOException {
saveUser(username, authenticationType, Role.USER.getRoleId());
}
public void saveUser(String username, AuthenticationType authenticationType, String role)
throws IllegalArgumentException, IOException {
if (!isUsernameValid(username)) {
throw new IllegalArgumentException(getInvalidUsernameMessage());
}
@@ -149,7 +144,7 @@ public class UserService implements UserServiceInterface {
user.setUsername(username);
user.setEnabled(true);
user.setFirstLogin(false);
user.addAuthority(new Authority(role, user));
user.addAuthority(new Authority(Role.USER.getRoleId(), user));
user.setAuthenticationType(authenticationType);
userRepository.save(user);
databaseBackupHelper.exportDatabase();
@@ -191,7 +186,7 @@ public class UserService implements UserServiceInterface {
}
public void deleteUser(String username) {
Optional<User> userOpt = findByUsernameIgnoreCase(username);
Optional<User> userOpt = userRepository.findByUsernameIgnoreCase(username);
if (userOpt.isPresent()) {
for (Authority authority : userOpt.get().getAuthorities()) {
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
@@ -200,20 +195,21 @@ public class UserService implements UserServiceInterface {
}
userRepository.delete(userOpt.get());
}
invalidateUserSessions(username);
}
public boolean usernameExists(String username) {
return findByUsername(username).isPresent();
return userRepository.findByUsername(username).isPresent();
}
public boolean usernameExistsIgnoreCase(String username) {
return findByUsernameIgnoreCase(username).isPresent();
return userRepository.findByUsernameIgnoreCase(username).isPresent();
}
public boolean hasUsers() {
long userCount = userRepository.count();
if (findByUsernameIgnoreCase(Role.INTERNAL_API_USER.getRoleId()).isPresent()) {
if (userRepository
.findByUsernameIgnoreCase(Role.INTERNAL_API_USER.getRoleId())
.isPresent()) {
userCount -= 1;
}
return userCount > 0;
@@ -221,7 +217,7 @@ public class UserService implements UserServiceInterface {
public void updateUserSettings(String username, Map<String, String> updates)
throws IOException {
Optional<User> userOpt = findByUsernameIgnoreCase(username);
Optional<User> userOpt = userRepository.findByUsernameIgnoreCase(username);
if (userOpt.isPresent()) {
User user = userOpt.get();
Map<String, String> settingsMap = user.getSettings();
@@ -272,17 +268,10 @@ public class UserService implements UserServiceInterface {
databaseBackupHelper.exportDatabase();
}
public void changeRole(User user, String newRole) throws IOException {
public void changeRole(User user, String newRole) {
Authority userAuthority = this.findRole(user);
userAuthority.setAuthority(newRole);
authorityRepository.save(userAuthority);
databaseBackupHelper.exportDatabase();
}
public void changeUserEnabled(User user, Boolean enbeled) throws IOException {
user.setEnabled(enbeled);
userRepository.save(user);
databaseBackupHelper.exportDatabase();
}
public boolean isPasswordCorrect(User user, String currentPassword) {
@@ -306,40 +295,14 @@ public class UserService implements UserServiceInterface {
}
public boolean hasPassword(String username) {
Optional<User> user = findByUsernameIgnoreCase(username);
Optional<User> user = userRepository.findByUsernameIgnoreCase(username);
return user.isPresent() && user.get().hasPassword();
}
public boolean isAuthenticationTypeByUsername(
String username, AuthenticationType authenticationType) {
Optional<User> user = findByUsernameIgnoreCase(username);
Optional<User> user = userRepository.findByUsernameIgnoreCase(username);
return user.isPresent()
&& authenticationType.name().equalsIgnoreCase(user.get().getAuthenticationType());
}
public boolean isUserDisabled(String username) {
Optional<User> userOpt = findByUsernameIgnoreCase(username);
return userOpt.map(user -> !user.isEnabled()).orElse(false);
}
public void invalidateUserSessions(String username) {
String usernameP = "";
for (Object principal : sessionRegistry.getAllPrincipals()) {
for (SessionInformation sessionsInformation :
sessionRegistry.getAllSessions(principal, false)) {
if (principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
usernameP = userDetails.getUsername();
} else if (principal instanceof OAuth2User) {
OAuth2User oAuth2User = (OAuth2User) principal;
usernameP = oAuth2User.getName();
} else if (principal instanceof String) {
usernameP = (String) principal;
}
if (usernameP.equalsIgnoreCase(username)) {
sessionRegistry.expireSession(sessionsInformation.getSessionId());
}
}
}
}
}

View File

@@ -2,8 +2,8 @@ package stirling.software.SPDF.config.security.oauth2;
import java.io.IOException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
@@ -13,34 +13,19 @@ import org.springframework.security.web.authentication.SimpleUrlAuthenticationFa
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CustomOAuth2AuthenticationFailureHandler
extends SimpleUrlAuthenticationFailureHandler {
private static final Logger logger =
LoggerFactory.getLogger(CustomOAuth2AuthenticationFailureHandler.class);
@Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
if (exception instanceof BadCredentialsException) {
log.error("BadCredentialsException", exception);
getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials");
return;
}
if (exception instanceof DisabledException) {
log.error("User is deactivated: ", exception);
getRedirectStrategy().sendRedirect(request, response, "/logout?userIsDisabled=true");
return;
}
if (exception instanceof LockedException) {
log.error("Account locked: ", exception);
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
return;
}
if (exception instanceof OAuth2AuthenticationException) {
OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
@@ -49,13 +34,17 @@ public class CustomOAuth2AuthenticationFailureHandler
if (error.getErrorCode().equals("Password must not be null")) {
errorCode = "userAlreadyExistsWeb";
}
log.error("OAuth2 Authentication error: " + errorCode);
log.error("OAuth2AuthenticationException", exception);
logger.error("OAuth2 Authentication error: " + errorCode);
getRedirectStrategy()
.sendRedirect(request, response, "/logout?erroroauth=" + errorCode);
return;
} else if (exception instanceof LockedException) {
logger.error("Account locked: ", exception);
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
return;
} else {
logger.error("Unhandled authentication exception", exception);
super.onAuthenticationFailure(request, response, exception);
}
log.error("Unhandled authentication exception", exception);
super.onAuthenticationFailure(request, response, exception);
}
}

View File

@@ -2,9 +2,10 @@ package stirling.software.SPDF.config.security.oauth2;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.SavedRequest;
@@ -25,6 +26,9 @@ public class CustomOAuth2AuthenticationSuccessHandler
private LoginAttemptService loginAttemptService;
private static final Logger logger =
LoggerFactory.getLogger(CustomOAuth2AuthenticationSuccessHandler.class);
private ApplicationProperties applicationProperties;
private UserService userService;
@@ -42,17 +46,6 @@ public class CustomOAuth2AuthenticationSuccessHandler
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
Object principal = authentication.getPrincipal();
String username = "";
if (principal instanceof OAuth2User) {
OAuth2User oauthUser = (OAuth2User) principal;
username = oauthUser.getName();
} else if (principal instanceof UserDetails) {
UserDetails oauthUser = (UserDetails) principal;
username = oauthUser.getUsername();
}
// Get the saved request
HttpSession session = request.getSession(false);
String contextPath = request.getContextPath();
@@ -66,8 +59,11 @@ public class CustomOAuth2AuthenticationSuccessHandler
// Redirect to the original destination
super.onAuthenticationSuccess(request, response, authentication);
} else {
OAuth2User oauthUser = (OAuth2User) authentication.getPrincipal();
OAUTH2 oAuth = applicationProperties.getSecurity().getOAUTH2();
String username = oauthUser.getName();
if (loginAttemptService.isBlocked(username)) {
if (session != null) {
session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
@@ -82,21 +78,15 @@ public class CustomOAuth2AuthenticationSuccessHandler
&& oAuth.getAutoCreateUser()) {
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
return;
}
try {
if (oAuth.getBlockRegistration()
&& !userService.usernameExistsIgnoreCase(username)) {
response.sendRedirect(contextPath + "/logout?oauth2_admin_blocked_user=true");
} else {
try {
userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser());
response.sendRedirect(contextPath + "/");
return;
} catch (IllegalArgumentException e) {
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
return;
}
if (principal instanceof OAuth2User) {
userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser());
}
response.sendRedirect(contextPath + "/");
return;
} catch (IllegalArgumentException e) {
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
return;
}
}
}

View File

@@ -2,26 +2,34 @@ package stirling.software.SPDF.config.security.oauth2;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import jakarta.servlet.http.HttpSession;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.SPDF.model.Provider;
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
import stirling.software.SPDF.utils.UrlUtils;
@Slf4j
public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
private static final Logger logger =
LoggerFactory.getLogger(CustomOAuth2LogoutSuccessHandler.class);
private final SessionRegistry sessionRegistry;
private final ApplicationProperties applicationProperties;
public CustomOAuth2LogoutSuccessHandler(ApplicationProperties applicationProperties) {
public CustomOAuth2LogoutSuccessHandler(
ApplicationProperties applicationProperties, SessionRegistry sessionRegistry) {
this.sessionRegistry = sessionRegistry;
this.applicationProperties = applicationProperties;
}
@@ -34,15 +42,6 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
String issuer = null;
String clientId = null;
if (authentication == null) {
if (request.getParameter("userIsDisabled") != null) {
response.sendRedirect(
request.getContextPath() + "/login?erroroauth=userIsDisabled");
} else {
super.onLogoutSuccess(request, response, authentication);
}
return;
}
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
if (authentication instanceof OAuth2AuthenticationToken) {
@@ -54,8 +53,9 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
issuer = provider.getIssuer();
clientId = provider.getClientId();
} catch (UnsupportedProviderException e) {
log.error(e.getMessage());
logger.error(e.getMessage());
}
} else {
registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
issuer = oauth.getIssuer();
@@ -70,16 +70,18 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
param = "erroroauth=" + sanitizeInput(errorMessage);
} else if (request.getParameter("oauth2AutoCreateDisabled") != null) {
param = "error=oauth2AutoCreateDisabled";
} else if (request.getParameter("oauth2_admin_blocked_user") != null) {
param = "erroroauth=oauth2_admin_blocked_user";
} else if (request.getParameter("userIsDisabled") != null) {
param = "erroroauth=userIsDisabled";
} else if (request.getParameter("badcredentials") != null) {
param = "error=badcredentials";
}
String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param;
HttpSession session = request.getSession(false);
if (session != null) {
String sessionId = session.getId();
sessionRegistry.removeSessionInformation(sessionId);
session.invalidate();
logger.info("Session invalidated: " + sessionId);
}
switch (registrationId.toLowerCase()) {
case "keycloak":
// Add Keycloak specific logout URL if needed
@@ -90,13 +92,13 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
+ clientId
+ "&post_logout_redirect_uri="
+ response.encodeRedirectURL(redirect_url);
log.info("Redirecting to Keycloak logout URL: " + logoutUrl);
logger.info("Redirecting to Keycloak logout URL: " + logoutUrl);
response.sendRedirect(logoutUrl);
break;
case "github":
// Add GitHub specific logout URL if needed
String githubLogoutUrl = "https://github.com/logout";
log.info("Redirecting to GitHub logout URL: " + githubLogoutUrl);
logger.info("Redirecting to GitHub logout URL: " + githubLogoutUrl);
response.sendRedirect(githubLogoutUrl);
break;
case "google":
@@ -104,14 +106,13 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
// String googleLogoutUrl =
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
// + response.encodeRedirectURL(redirect_url);
log.info("Google does not have a specific logout URL");
// log.info("Redirecting to Google logout URL: " + googleLogoutUrl);
// logger.info("Redirecting to Google logout URL: " + googleLogoutUrl);
// response.sendRedirect(googleLogoutUrl);
// break;
default:
String defaultRedirectUrl = request.getContextPath() + "/login?" + param;
log.info("Redirecting to default logout URL: " + defaultRedirectUrl);
response.sendRedirect(defaultRedirectUrl);
String redirectUrl = request.getContextPath() + "/login?" + param;
logger.info("Redirecting to default logout URL: " + redirectUrl);
response.sendRedirect(redirectUrl);
break;
}
}

View File

@@ -1,26 +0,0 @@
package stirling.software.SPDF.config.security.session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class CustomHttpSessionListener implements HttpSessionListener {
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
@Override
public void sessionCreated(HttpSessionEvent se) {
log.info("Session created: " + se.getSession().getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
log.info("Session destroyed: " + se.getSession().getId());
sessionPersistentRegistry.expireSession(se.getSession().getId());
}
}

View File

@@ -1,183 +0,0 @@
package stirling.software.SPDF.config.security.session;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Component;
import jakarta.transaction.Transactional;
import stirling.software.SPDF.model.SessionEntity;
@Component
public class SessionPersistentRegistry implements SessionRegistry {
private final SessionRepository sessionRepository;
@Value("${server.servlet.session.timeout:30m}")
private Duration defaultMaxInactiveInterval;
public SessionPersistentRegistry(SessionRepository sessionRepository) {
this.sessionRepository = sessionRepository;
}
@Override
public List<Object> getAllPrincipals() {
List<SessionEntity> sessions = sessionRepository.findAll();
List<Object> principals = new ArrayList<>();
for (SessionEntity session : sessions) {
principals.add(session.getPrincipalName());
}
return principals;
}
@Override
public List<SessionInformation> getAllSessions(
Object principal, boolean includeExpiredSessions) {
List<SessionInformation> sessionInformations = new ArrayList<>();
String principalName = null;
if (principal instanceof UserDetails) {
principalName = ((UserDetails) principal).getUsername();
} else if (principal instanceof OAuth2User) {
principalName = ((OAuth2User) principal).getName();
} else if (principal instanceof String) {
principalName = (String) principal;
}
if (principalName != null) {
List<SessionEntity> sessionEntities =
sessionRepository.findByPrincipalName(principalName);
for (SessionEntity sessionEntity : sessionEntities) {
if (includeExpiredSessions || !sessionEntity.isExpired()) {
sessionInformations.add(
new SessionInformation(
sessionEntity.getPrincipalName(),
sessionEntity.getSessionId(),
sessionEntity.getLastRequest()));
}
}
}
return sessionInformations;
}
@Override
@Transactional
public void registerNewSession(String sessionId, Object principal) {
String principalName = null;
if (principal instanceof UserDetails) {
principalName = ((UserDetails) principal).getUsername();
} else if (principal instanceof OAuth2User) {
principalName = ((OAuth2User) principal).getName();
} else if (principal instanceof String) {
principalName = (String) principal;
}
if (principalName != null) {
SessionEntity sessionEntity = new SessionEntity();
sessionEntity.setSessionId(sessionId);
sessionEntity.setPrincipalName(principalName);
sessionEntity.setLastRequest(new Date()); // Set lastRequest to the current date
sessionEntity.setExpired(false);
sessionRepository.save(sessionEntity);
}
}
@Override
@Transactional
public void removeSessionInformation(String sessionId) {
sessionRepository.deleteById(sessionId);
}
@Override
@Transactional
public void refreshLastRequest(String sessionId) {
Optional<SessionEntity> sessionEntityOpt = sessionRepository.findById(sessionId);
if (sessionEntityOpt.isPresent()) {
SessionEntity sessionEntity = sessionEntityOpt.get();
sessionEntity.setLastRequest(new Date()); // Update lastRequest to the current date
sessionRepository.save(sessionEntity);
}
}
@Override
public SessionInformation getSessionInformation(String sessionId) {
Optional<SessionEntity> sessionEntityOpt = sessionRepository.findById(sessionId);
if (sessionEntityOpt.isPresent()) {
SessionEntity sessionEntity = sessionEntityOpt.get();
return new SessionInformation(
sessionEntity.getPrincipalName(),
sessionEntity.getSessionId(),
sessionEntity.getLastRequest());
}
return null;
}
// Retrieve all non-expired sessions
public List<SessionEntity> getAllSessionsNotExpired() {
return sessionRepository.findByExpired(false);
}
// Retrieve all sessions
public List<SessionEntity> getAllSessions() {
return sessionRepository.findAll();
}
// Mark a session as expired
public void expireSession(String sessionId) {
Optional<SessionEntity> sessionEntityOpt = sessionRepository.findById(sessionId);
if (sessionEntityOpt.isPresent()) {
SessionEntity sessionEntity = sessionEntityOpt.get();
sessionEntity.setExpired(true); // Set expired to true
sessionRepository.save(sessionEntity);
}
}
// Get the maximum inactive interval for sessions
public int getMaxInactiveInterval() {
return (int) defaultMaxInactiveInterval.getSeconds();
}
// Retrieve a session entity by session ID
public SessionEntity getSessionEntity(String sessionId) {
return sessionRepository.findBySessionId(sessionId);
}
// Update session details by principal name
public void updateSessionByPrincipalName(
String principalName, boolean expired, Date lastRequest) {
sessionRepository.saveByPrincipalName(expired, lastRequest, principalName);
}
// Find the latest session for a given principal name
public Optional<SessionEntity> findLatestSession(String principalName) {
List<SessionEntity> allSessions = sessionRepository.findByPrincipalName(principalName);
if (allSessions.isEmpty()) {
return Optional.empty();
}
// Sort sessions by lastRequest in descending order
Collections.sort(
allSessions,
new Comparator<SessionEntity>() {
@Override
public int compare(SessionEntity s1, SessionEntity s2) {
// Sort by lastRequest in descending order
return s2.getLastRequest().compareTo(s1.getLastRequest());
}
});
// The first session in the list is the latest session for the given principal name
return Optional.of(allSessions.get(0));
}
}

View File

@@ -1,20 +0,0 @@
package stirling.software.SPDF.config.security.session;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.session.SessionRegistryImpl;
@Configuration
public class SessionRegistryConfig {
@Bean
public SessionRegistryImpl sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public SessionPersistentRegistry sessionPersistentRegistry(
SessionRepository sessionRepository) {
return new SessionPersistentRegistry(sessionRepository);
}
}

View File

@@ -1,31 +0,0 @@
package stirling.software.SPDF.config.security.session;
import java.util.Date;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import jakarta.transaction.Transactional;
import stirling.software.SPDF.model.SessionEntity;
@Repository
public interface SessionRepository extends JpaRepository<SessionEntity, String> {
List<SessionEntity> findByPrincipalName(String principalName);
List<SessionEntity> findByExpired(boolean expired);
SessionEntity findBySessionId(String sessionId);
@Modifying
@Transactional
@Query(
"UPDATE SessionEntity s SET s.expired = :expired, s.lastRequest = :lastRequest WHERE s.principalName = :principalName")
void saveByPrincipalName(
@Param("expired") boolean expired,
@Param("lastRequest") Date lastRequest,
@Param("principalName") String principalName);
}

View File

@@ -1,35 +0,0 @@
package stirling.software.SPDF.config.security.session;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.stereotype.Component;
@Component
public class SessionScheduled {
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
@Scheduled(cron = "0 0/5 * * * ?")
public void expireSessions() {
Instant now = Instant.now();
for (Object principal : sessionPersistentRegistry.getAllPrincipals()) {
List<SessionInformation> sessionInformations =
sessionPersistentRegistry.getAllSessions(principal, false);
for (SessionInformation sessionInformation : sessionInformations) {
Date lastRequest = sessionInformation.getLastRequest();
int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval();
Instant expirationTime =
lastRequest.toInstant().plus(maxInactiveInterval, ChronoUnit.SECONDS);
if (now.isAfter(expirationTime)) {
sessionPersistentRegistry.expireSession(sessionInformation.getSessionId());
}
}
}
}
}

View File

@@ -1,82 +0,0 @@
package stirling.software.SPDF.controller.api;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.service.PdfImageRemovalService;
import stirling.software.SPDF.utils.WebResponseUtils;
/**
* Controller class for handling PDF image removal requests. Provides an endpoint to remove images
* from a PDF file to reduce its size.
*/
@RestController
@RequestMapping("/api/v1/general")
public class PdfImageRemovalController {
// Service for removing images from PDFs
@Autowired private PdfImageRemovalService pdfImageRemovalService;
/**
* Constructor for dependency injection of PdfImageRemovalService.
*
* @param pdfImageRemovalService The service used for removing images from PDFs.
*/
public PdfImageRemovalController(PdfImageRemovalService pdfImageRemovalService) {
this.pdfImageRemovalService = pdfImageRemovalService;
}
/**
* Endpoint to remove images from a PDF file.
*
* <p>This method processes the uploaded PDF file, removes all images, and returns the modified
* PDF file with a new name indicating that images were removed.
*
* @param file The PDF file with images to be removed.
* @return ResponseEntity containing the modified PDF file as byte array with appropriate
* content type and filename.
* @throws IOException If an error occurs while processing the PDF file.
*/
@PostMapping(consumes = "multipart/form-data", value = "/remove-image-pdf")
@Operation(
summary = "Remove images from file to reduce the file size.",
description =
"This endpoint remove images from file to reduce the file size.Input:PDF Output:PDF Type:MISO")
public ResponseEntity<byte[]> removeImages(@ModelAttribute PDFFile file) throws IOException {
MultipartFile pdf = file.getFileInput();
// Convert the MultipartFile to a byte array
byte[] pdfBytes = pdf.getBytes();
// Load the PDF document from the byte array
PDDocument document = Loader.loadPDF(pdfBytes);
// Remove images from the PDF document using the service
PDDocument modifiedDocument = pdfImageRemovalService.removeImagesFromPdf(document);
// Create a ByteArrayOutputStream to hold the modified PDF data
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// Save the modified PDF document to the output stream
modifiedDocument.save(outputStream);
modifiedDocument.close();
// Generate a new filename for the modified PDF
String mergedFileName =
pdf.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_removed_images.pdf";
// Convert the byte array to a web response and return it
return WebResponseUtils.bytesToWebResponse(outputStream.toByteArray(), mergedFileName);
}
}

View File

@@ -3,7 +3,6 @@ package stirling.software.SPDF.controller.api;
import java.io.IOException;
import java.security.Principal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -13,8 +12,8 @@ import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@@ -31,8 +30,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.User;
import stirling.software.SPDF.model.api.user.UsernameAndPass;
@@ -44,8 +41,6 @@ public class UserController {
@Autowired private UserService userService;
@Autowired SessionPersistentRegistry sessionRegistry;
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/register")
public String register(@ModelAttribute UsernameAndPass requestModel, Model model)
@@ -208,10 +203,9 @@ public class UserController {
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/admin/saveUser")
public RedirectView saveUser(
@RequestParam String username,
@RequestParam(name = "password", required = false) String password,
@RequestParam(name = "username") String username,
@RequestParam(name = "password") String password,
@RequestParam(name = "role") String role,
@RequestParam(name = "authType") String authType,
@RequestParam(name = "forceChange", required = false, defaultValue = "false")
boolean forceChange)
throws IllegalArgumentException, IOException {
@@ -243,15 +237,7 @@ public class UserController {
return new RedirectView("/addUsers?messageType=invalidRole", true);
}
if (authType.equalsIgnoreCase(AuthenticationType.OAUTH2.toString())) {
userService.saveUser(username, AuthenticationType.OAUTH2, role);
} else {
if (password.isBlank()) {
return new RedirectView("/addUsers?messageType=invalidPassword", true);
}
userService.saveUser(username, password, role, forceChange);
}
userService.saveUser(username, password, role, forceChange);
return new RedirectView(
"/addUsers", true); // Redirect to account page after adding the user
}
@@ -261,8 +247,7 @@ public class UserController {
public RedirectView changeRole(
@RequestParam(name = "username") String username,
@RequestParam(name = "role") String role,
Authentication authentication)
throws IOException {
Authentication authentication) {
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
@@ -293,60 +278,6 @@ public class UserController {
User user = userOpt.get();
userService.changeRole(user, role);
return new RedirectView(
"/addUsers", true); // Redirect to account page after adding the user
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/admin/changeUserEnabled/{username}")
public RedirectView changeUserEnabled(
@PathVariable("username") String username,
@RequestParam("enabled") boolean enabled,
Authentication authentication)
throws IOException {
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
if (!userOpt.isPresent()) {
return new RedirectView("/addUsers?messageType=userNotFound", true);
}
if (!userService.usernameExistsIgnoreCase(username)) {
return new RedirectView("/addUsers?messageType=userNotFound", true);
}
// Get the currently authenticated username
String currentUsername = authentication.getName();
// Check if the provided username matches the current session's username
if (currentUsername.equalsIgnoreCase(username)) {
return new RedirectView("/addUsers?messageType=disabledCurrentUser", true);
}
User user = userOpt.get();
userService.changeUserEnabled(user, enabled);
if (!enabled) {
// Invalidate all sessions if the user is being disabled
List<Object> principals = sessionRegistry.getAllPrincipals();
String userNameP = "";
for (Object principal : principals) {
List<SessionInformation> sessionsInformations =
sessionRegistry.getAllSessions(principal, false);
if (principal instanceof UserDetails) {
userNameP = ((UserDetails) principal).getUsername();
} else if (principal instanceof OAuth2User) {
userNameP = ((OAuth2User) principal).getName();
} else if (principal instanceof String) {
userNameP = (String) principal;
}
if (userNameP.equalsIgnoreCase(username)) {
for (SessionInformation sessionsInformation : sessionsInformations) {
sessionRegistry.expireSession(sessionsInformation.getSessionId());
}
}
}
}
return new RedirectView(
"/addUsers", true); // Redirect to account page after adding the user
}
@@ -354,7 +285,7 @@ public class UserController {
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/admin/deleteUser/{username}")
public RedirectView deleteUser(
@PathVariable("username") String username, Authentication authentication) {
@PathVariable(name = "username") String username, Authentication authentication) {
if (!userService.usernameExistsIgnoreCase(username)) {
return new RedirectView("/addUsers?messageType=deleteUsernameExists", true);
@@ -367,18 +298,27 @@ public class UserController {
if (currentUsername.equalsIgnoreCase(username)) {
return new RedirectView("/addUsers?messageType=deleteCurrentUser", true);
}
// Invalidate all sessions before deleting the user
List<SessionInformation> sessionsInformations =
sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
for (SessionInformation sessionsInformation : sessionsInformations) {
sessionRegistry.expireSession(sessionsInformation.getSessionId());
sessionRegistry.removeSessionInformation(sessionsInformation.getSessionId());
}
invalidateUserSessions(username);
userService.deleteUser(username);
return new RedirectView("/addUsers", true);
}
@Autowired private SessionRegistry sessionRegistry;
private void invalidateUserSessions(String username) {
for (Object principal : sessionRegistry.getAllPrincipals()) {
if (principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
if (userDetails.getUsername().equals(username)) {
for (SessionInformation session :
sessionRegistry.getAllSessions(principal, false)) {
session.expireNow();
}
}
}
}
}
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
@PostMapping("/get-api-key")
public ResponseEntity<String> getApiKey(Principal principal) {

View File

@@ -1,18 +1,8 @@
package stirling.software.SPDF.controller.api.converters;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.pdfbox.rendering.ImageType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -30,10 +20,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
import stirling.software.SPDF.utils.CheckProgramInstall;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@@ -73,87 +60,15 @@ public class ConvertImgPDFController {
result =
PdfUtils.convertFromPdf(
pdfBytes,
imageFormat.equalsIgnoreCase("webp") ? "png" : imageFormat.toUpperCase(),
imageFormat.toUpperCase(),
colorTypeResult,
singleImage,
Integer.valueOf(dpi),
filename);
if (result == null || result.length == 0) {
logger.error("resultant bytes for {} is null, error converting ", filename);
}
if (imageFormat.equalsIgnoreCase("webp") && !CheckProgramInstall.isPythonAvailable()) {
throw new IOException("Python is not installed. Required for WebP conversion.");
} else if (imageFormat.equalsIgnoreCase("webp")
&& CheckProgramInstall.isPythonAvailable()) {
// Write the output stream to a temp file
Path tempFile = Files.createTempFile("temp_png", ".png");
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
fos.write(result);
fos.flush();
}
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
List<String> command = new ArrayList<>();
command.add(pythonVersion);
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
// Create a temporary directory for the output WebP files
Path tempOutputDir = Files.createTempDirectory("webp_output");
if (singleImage) {
// Run the Python script to convert PNG to WebP
command.add(tempFile.toString());
command.add(tempOutputDir.toString());
command.add("--single");
} else {
// Save the uploaded PDF to a temporary file
Path tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
file.transferTo(tempPdfPath.toFile());
// Run the Python script to convert PDF to WebP
command.add(tempPdfPath.toString());
command.add(tempOutputDir.toString());
}
command.add("--dpi");
command.add(dpi);
ProcessExecutorResult resultProcess =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(command);
// Find all WebP files in the output directory
List<Path> webpFiles =
Files.walk(tempOutputDir)
.filter(path -> path.toString().endsWith(".webp"))
.collect(Collectors.toList());
if (webpFiles.isEmpty()) {
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
throw new IOException("No WebP files were created. " + resultProcess.getMessages());
}
byte[] bodyBytes = new byte[0];
if (webpFiles.size() == 1) {
// Return the single WebP file directly
Path webpFilePath = webpFiles.get(0);
bodyBytes = Files.readAllBytes(webpFilePath);
} else {
// Create a ZIP file containing all WebP images
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
for (Path webpFile : webpFiles) {
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
Files.copy(webpFile, zos);
zos.closeEntry();
}
}
bodyBytes = zipOutputStream.toByteArray();
}
// Clean up the temporary files
Files.deleteIfExists(tempFile);
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
result = bodyBytes;
}
if (singleImage) {
String docName = filename + "." + imageFormat;
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));

View File

@@ -39,12 +39,6 @@ public class ConvertWebsiteToPDF {
if (!URL.matches("^https?://.*") || !GeneralUtils.isValidURL(URL)) {
throw new IllegalArgumentException("Invalid URL format provided.");
}
// validate the URL is reachable
if (!GeneralUtils.isURLReachable(URL)) {
throw new IllegalArgumentException("URL is not reachable, please provide a valid URL.");
}
Path tempOutputFile = null;
byte[] pdfBytes;
try {

View File

@@ -32,7 +32,6 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest;
import stirling.software.SPDF.utils.CheckProgramInstall;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils;
@@ -77,11 +76,6 @@ public class ExtractImageScansController {
Path tempZipFile = null;
List<Path> tempDirs = new ArrayList<>();
if (!CheckProgramInstall.isPythonAvailable()) {
throw new IOException("Python is not installed.");
}
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
try {
// Check if input file is a PDF
if ("pdf".equalsIgnoreCase(extension)) {
@@ -123,7 +117,7 @@ public class ExtractImageScansController {
List<String> command =
new ArrayList<>(
Arrays.asList(
pythonVersion,
"python3",
"./scripts/split_photos.py",
images.get(i),
tempDir.toString(),

View File

@@ -1,16 +1,13 @@
package stirling.software.SPDF.controller.api.misc;
import java.awt.*;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -50,19 +47,16 @@ public class ExtractImagesController {
@Operation(
summary = "Extract images from a PDF file",
description =
"This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input: PDF Output: IMAGE/ZIP Type: SIMO")
"This endpoint extracts images from a given PDF file and returns them in a zip file. Users can specify the output image format. Input:PDF Output:IMAGE/ZIP Type:SIMO")
public ResponseEntity<byte[]> extractImages(@ModelAttribute PDFWithImageFormatRequest request)
throws IOException, InterruptedException, ExecutionException {
throws IOException {
MultipartFile file = request.getFileInput();
String format = request.getFormat();
System.out.println(
System.currentTimeMillis() + " file=" + file.getName() + ", format=" + format);
System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
PDDocument document = Loader.loadPDF(file.getBytes());
// Determine if multithreading should be used based on PDF size or number of pages
boolean useMultithreading = shouldUseMultithreading(file, document);
// Create ByteArrayOutputStream to write zip file to byte array
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -72,51 +66,71 @@ public class ExtractImagesController {
// Set compression level
zos.setLevel(Deflater.BEST_COMPRESSION);
int imageIndex = 1;
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
int pageNum = 0;
Set<Integer> processedImages = new HashSet<>();
// Iterate over each page
for (PDPage page : document.getPages()) {
++pageNum;
// Extract images from page
for (COSName name : page.getResources().getXObjectNames()) {
if (page.getResources().isImageXObject(name)) {
PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);
int imageHash = image.hashCode();
if (processedImages.contains(imageHash)) {
continue; // Skip already processed images
}
processedImages.add(imageHash);
if (useMultithreading) {
// Executor service to handle multithreading
ExecutorService executor =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
Set<Future<Void>> futures = new HashSet<>();
// Convert image to desired format
RenderedImage renderedImage = image.getImage();
BufferedImage bufferedImage = null;
if ("png".equalsIgnoreCase(format)) {
bufferedImage =
new BufferedImage(
renderedImage.getWidth(),
renderedImage.getHeight(),
BufferedImage.TYPE_INT_ARGB);
} else if ("jpeg".equalsIgnoreCase(format) || "jpg".equalsIgnoreCase(format)) {
bufferedImage =
new BufferedImage(
renderedImage.getWidth(),
renderedImage.getHeight(),
BufferedImage.TYPE_INT_RGB);
} else if ("gif".equalsIgnoreCase(format)) {
bufferedImage =
new BufferedImage(
renderedImage.getWidth(),
renderedImage.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED);
}
// Iterate over each page
for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) {
PDPage page = document.getPage(pgNum);
int pageNum = document.getPages().indexOf(page) + 1;
// Submit a task for processing each page
Future<Void> future =
executor.submit(
() -> {
extractImagesFromPage(
page, format, filename, pageNum, processedImages, zos);
return null;
});
// Write image to zip file
String imageName =
filename + "_" + imageIndex + " (Page " + pageNum + ")." + format;
ZipEntry zipEntry = new ZipEntry(imageName);
zos.putNextEntry(zipEntry);
futures.add(future);
}
Graphics2D g = bufferedImage.createGraphics();
g.drawImage((Image) renderedImage, 0, 0, null);
g.dispose();
// Write image bytes to zip file
ByteArrayOutputStream imageBaos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, format, imageBaos);
zos.write(imageBaos.toByteArray());
// Wait for all tasks to complete
for (Future<Void> future : futures) {
future.get();
}
// Close executor service
executor.shutdown();
} else {
// Single-threaded extraction
for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) {
PDPage page = document.getPage(pgNum);
extractImagesFromPage(page, format, filename, pgNum + 1, processedImages, zos);
zos.closeEntry();
imageIndex++;
}
}
}
// Close PDDocument and ZipOutputStream
document.close();
// Close ZipOutputStream and PDDocument
zos.close();
document.close();
// Create ByteArrayResource from byte array
byte[] zipContents = baos.toByteArray();
@@ -124,72 +138,4 @@ public class ExtractImagesController {
return WebResponseUtils.boasToWebResponse(
baos, filename + "_extracted-images.zip", MediaType.APPLICATION_OCTET_STREAM);
}
private boolean shouldUseMultithreading(MultipartFile file, PDDocument document) {
// Criteria: Use multithreading if file size > 10MB or number of pages > 20
long fileSizeInMB = file.getSize() / (1024 * 1024);
int numberOfPages = document.getPages().getCount();
return fileSizeInMB > 10 || numberOfPages > 20;
}
private void extractImagesFromPage(
PDPage page,
String format,
String filename,
int pageNum,
Set<Integer> processedImages,
ZipOutputStream zos)
throws IOException {
if (page.getResources() == null || page.getResources().getXObjectNames() == null) {
return;
}
for (COSName name : page.getResources().getXObjectNames()) {
if (page.getResources().isImageXObject(name)) {
PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);
int imageHash = image.hashCode();
synchronized (processedImages) {
if (processedImages.contains(imageHash)) {
continue; // Skip already processed images
}
processedImages.add(imageHash);
}
RenderedImage renderedImage = image.getImage();
// Convert to standard RGB colorspace if needed
BufferedImage bufferedImage = convertToRGB(renderedImage, format);
// Write image to zip file
String imageName = filename + "_" + imageHash + " (Page " + pageNum + ")." + format;
synchronized (zos) {
zos.putNextEntry(new ZipEntry(imageName));
ByteArrayOutputStream imageBaos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, format, imageBaos);
zos.write(imageBaos.toByteArray());
zos.closeEntry();
}
}
}
}
private BufferedImage convertToRGB(RenderedImage renderedImage, String format) {
int width = renderedImage.getWidth();
int height = renderedImage.getHeight();
BufferedImage rgbImage;
if ("png".equalsIgnoreCase(format)) {
rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
} else if ("jpeg".equalsIgnoreCase(format) || "jpg".equalsIgnoreCase(format)) {
rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
} else if ("gif".equalsIgnoreCase(format)) {
rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED);
} else {
rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
}
Graphics2D g = rgbImage.createGraphics();
g.drawImage((Image) renderedImage, 0, 0, null);
g.dispose();
return rgbImage;
}
}

View File

@@ -13,7 +13,8 @@ import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -26,7 +27,6 @@ import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
@@ -37,10 +37,10 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class OCRController {
@Autowired ApplicationProperties applicationProperties;
private static final Logger logger = LoggerFactory.getLogger(OCRController.class);
public List<String> getAvailableTesseractLanguages() {
String tessdataDir = applicationProperties.getSystem().getTessdataDir();
String tessdataDir = "/usr/share/tessdata";
File[] files = new File(tessdataDir).listFiles();
if (files == null) {
return Collections.emptyList();

View File

@@ -43,7 +43,7 @@ public class ApiDocService {
Map<String, List<String>> outputToFileTypes = new HashMap<>();
public List<String> getExtensionTypes(boolean output, String operationName) {
public List getExtensionTypes(boolean output, String operationName) {
if (outputToFileTypes.size() == 0) {
outputToFileTypes.put("PDF", Arrays.asList("pdf"));
outputToFileTypes.put(

View File

@@ -1,14 +1,21 @@
package stirling.software.SPDF.controller.api.security;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
@@ -25,7 +32,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.PDFText;
import stirling.software.SPDF.model.api.security.RedactPdfRequest;
import stirling.software.SPDF.pdf.TextFinder;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@@ -75,9 +81,22 @@ public class RedactController {
}
if (convertPDFToImage) {
PDDocument convertedPdf = PdfUtils.convertPdfToPdfImage(document);
PDDocument imageDocument = new PDDocument();
PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true);
for (int page = 0; page < document.getNumberOfPages(); ++page) {
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight()));
imageDocument.addPage(newPage);
PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim);
PDPageContentStream contentStream =
new PDPageContentStream(
imageDocument, newPage, AppendMode.APPEND, true, true);
contentStream.drawImage(pdImage, 0, 0);
contentStream.close();
}
document.close();
document = convertedPdf;
document = imageDocument;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();

View File

@@ -36,7 +36,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.security.AddWatermarkRequest;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@@ -61,7 +60,6 @@ public class WatermarkController {
float opacity = request.getOpacity();
int widthSpacer = request.getWidthSpacer();
int heightSpacer = request.getHeightSpacer();
boolean convertPdfToImage = request.isConvertPDFToImage();
// Load the input PDF
PDDocument document = Loader.loadPDF(pdfFile.getBytes());
@@ -106,12 +104,6 @@ public class WatermarkController {
contentStream.close();
}
if (convertPdfToImage) {
PDDocument convertedPdf = PdfUtils.convertPdfToPdfImage(document);
document.close();
document = convertedPdf;
}
return WebResponseUtils.pdfDocToWebResponse(
document,
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())

View File

@@ -1,15 +1,13 @@
package stirling.software.SPDF.controller.web;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
@@ -25,14 +23,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
import stirling.software.SPDF.model.Authority;
import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.SessionEntity;
import stirling.software.SPDF.model.User;
import stirling.software.SPDF.model.provider.GithubProvider;
import stirling.software.SPDF.model.provider.GoogleProvider;
@@ -40,20 +35,15 @@ import stirling.software.SPDF.model.provider.KeycloakProvider;
import stirling.software.SPDF.repository.UserRepository;
@Controller
@Slf4j
@Tag(name = "Account Security", description = "Account Security APIs")
public class AccountWebController {
@Autowired ApplicationProperties applicationProperties;
@Autowired SessionPersistentRegistry sessionPersistentRegistry;
@Autowired
private UserRepository userRepository; // Assuming you have a repository for user operations
private static final Logger logger = LoggerFactory.getLogger(AccountWebController.class);
@GetMapping("/login")
public String login(HttpServletRequest request, Model model, Authentication authentication) {
// If the user is already authenticated, redirect them to the home page.
if (authentication != null && authentication.isAuthenticated()) {
return "redirect:/";
}
@@ -147,13 +137,6 @@ public class AccountWebController {
break;
case "invalid_id_token":
erroroauth = "login.oauth2InvalidIdToken";
break;
case "oauth2_admin_blocked_user":
erroroauth = "login.oauth2AdminBlockedUser";
break;
case "userIsDisabled":
erroroauth = "login.userIsDisabled";
break;
default:
break;
}
@@ -172,6 +155,9 @@ public class AccountWebController {
return "login";
}
@Autowired
private UserRepository userRepository; // Assuming you have a repository for user operations
@PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping("/addUsers")
public String showAddUserForm(
@@ -180,13 +166,6 @@ public class AccountWebController {
Iterator<User> iterator = allUsers.iterator();
Map<String, String> roleDetails = Role.getAllRoleDetails();
// Map to store session information and user activity status
Map<String, Boolean> userSessions = new HashMap<>();
Map<String, Date> userLastRequest = new HashMap<>();
int activeUsers = 0;
int disabledUsers = 0;
while (iterator.hasNext()) {
User user = iterator.next();
if (user != null) {
@@ -197,73 +176,9 @@ public class AccountWebController {
break; // Break out of the inner loop once the user is removed
}
}
// Determine the user's session status and last request time
int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval();
boolean hasActiveSession = false;
Date lastRequest = null;
Optional<SessionEntity> latestSession =
sessionPersistentRegistry.findLatestSession(user.getUsername());
if (latestSession.isPresent()) {
SessionEntity sessionEntity = latestSession.get();
Date lastAccessedTime = sessionEntity.getLastRequest();
Instant now = Instant.now();
// Calculate session expiration and update session status accordingly
Instant expirationTime =
lastAccessedTime
.toInstant()
.plus(maxInactiveInterval, ChronoUnit.SECONDS);
if (now.isAfter(expirationTime)) {
sessionPersistentRegistry.expireSession(sessionEntity.getSessionId());
hasActiveSession = false;
} else {
hasActiveSession = !sessionEntity.isExpired();
}
lastRequest = sessionEntity.getLastRequest();
} else {
hasActiveSession = false;
lastRequest = new Date(0); // No session, set default last request time
}
userSessions.put(user.getUsername(), hasActiveSession);
userLastRequest.put(user.getUsername(), lastRequest);
if (hasActiveSession) {
activeUsers++;
}
if (!user.isEnabled()) {
disabledUsers++;
}
}
}
// Sort users by active status and last request date
List<User> sortedUsers =
allUsers.stream()
.sorted(
(u1, u2) -> {
boolean u1Active = userSessions.get(u1.getUsername());
boolean u2Active = userSessions.get(u2.getUsername());
if (u1Active && !u2Active) {
return -1;
} else if (!u1Active && u2Active) {
return 1;
} else {
Date u1LastRequest =
userLastRequest.getOrDefault(
u1.getUsername(), new Date(0));
Date u2LastRequest =
userLastRequest.getOrDefault(
u2.getUsername(), new Date(0));
return u2LastRequest.compareTo(u1LastRequest);
}
})
.collect(Collectors.toList());
String messageType = request.getParameter("messageType");
String deleteMessage = null;
@@ -288,9 +203,6 @@ public class AccountWebController {
case "invalidUsername":
addMessage = "invalidUsernameMessage";
break;
case "invalidPassword":
addMessage = "invalidPasswordMessage";
break;
default:
break;
}
@@ -306,24 +218,16 @@ public class AccountWebController {
case "downgradeCurrentUser":
changeMessage = "downgradeCurrentUserMessage";
break;
case "disabledCurrentUser":
changeMessage = "disabledCurrentUserMessage";
break;
default:
changeMessage = messageType;
break;
}
model.addAttribute("changeMessage", changeMessage);
}
model.addAttribute("users", sortedUsers);
model.addAttribute("users", allUsers);
model.addAttribute("currentUsername", authentication.getName());
model.addAttribute("roleDetails", roleDetails);
model.addAttribute("userSessions", userSessions);
model.addAttribute("userLastRequest", userLastRequest);
model.addAttribute("totalUsers", allUsers.size());
model.addAttribute("activeUsers", activeUsers);
model.addAttribute("disabledUsers", disabledUsers);
return "addUsers";
}
@@ -374,7 +278,7 @@ public class AccountWebController {
settingsJson = objectMapper.writeValueAsString(user.get().getSettings());
} catch (JsonProcessingException e) {
// Handle JSON conversion error
log.error("exception", e);
logger.error("exception", e);
return "redirect:/error";
}

View File

@@ -9,8 +9,6 @@ import org.springframework.web.servlet.ModelAndView;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.utils.CheckProgramInstall;
@Controller
@Tag(name = "Convert", description = "Convert APIs")
public class ConverterWebController {
@@ -23,6 +21,14 @@ public class ConverterWebController {
return "convert/book-to-pdf";
}
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
@GetMapping("/pdf-to-book")
@Hidden
public String convertPdfToBookForm(Model model) {
model.addAttribute("currentPage", "pdf-to-book");
return "convert/pdf-to-book";
}
@GetMapping("/img-to-pdf")
@Hidden
public String convertImgToPdfForm(Model model) {
@@ -51,6 +57,13 @@ public class ConverterWebController {
return "convert/url-to-pdf";
}
@GetMapping("/pdf-to-img")
@Hidden
public String pdfToimgForm(Model model) {
model.addAttribute("currentPage", "pdf-to-img");
return "convert/pdf-to-img";
}
@GetMapping("/file-to-pdf")
@Hidden
public String convertToPdfForm(Model model) {
@@ -60,23 +73,6 @@ public class ConverterWebController {
// PDF TO......
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
@GetMapping("/pdf-to-book")
@Hidden
public String convertPdfToBookForm(Model model) {
model.addAttribute("currentPage", "pdf-to-book");
return "convert/pdf-to-book";
}
@GetMapping("/pdf-to-img")
@Hidden
public String pdfToimgForm(Model model) {
boolean isPython = CheckProgramInstall.isPythonAvailable();
model.addAttribute("isPython", isPython);
model.addAttribute("currentPage", "pdf-to-img");
return "convert/pdf-to-img";
}
@GetMapping("/pdf-to-html")
@Hidden
public ModelAndView pdfToHTML() {

View File

@@ -310,11 +310,4 @@ public class GeneralWebController {
model.addAttribute("currentPage", "auto-split-pdf");
return "auto-split-pdf";
}
@GetMapping("/remove-image-pdf")
@Hidden
public String removeImagePdfForm(Model model) {
model.addAttribute("currentPage", "remove-image-pdf");
return "remove-image-pdf";
}
}

View File

@@ -6,7 +6,6 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@@ -15,15 +14,10 @@ import org.springframework.web.servlet.ModelAndView;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.utils.CheckProgramInstall;
@Controller
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class OtherWebController {
@Autowired ApplicationProperties applicationProperties;
@GetMapping("/compress-pdf")
@Hidden
public String compressPdfForm(Model model) {
@@ -35,8 +29,6 @@ public class OtherWebController {
@Hidden
public ModelAndView extractImageScansForm() {
ModelAndView modelAndView = new ModelAndView("misc/extract-image-scans");
boolean isPython = CheckProgramInstall.isPythonAvailable();
modelAndView.addObject("isPython", isPython);
modelAndView.addObject("currentPage", "extract-image-scans");
return modelAndView;
}
@@ -105,7 +97,7 @@ public class OtherWebController {
}
public List<String> getAvailableTesseractLanguages() {
String tessdataDir = applicationProperties.getSystem().getTessdataDir();
String tessdataDir = "/usr/share/tessdata";
File[] files = new File(tessdataDir).listFiles();
if (files == null) {
return Collections.emptyList();

View File

@@ -241,7 +241,6 @@ public class ApplicationProperties {
private String clientId;
private String clientSecret;
private Boolean autoCreateUser = false;
private Boolean blockRegistration = false;
private String useAsUsername;
private Collection<String> scopes = new ArrayList<>();
private String provider;
@@ -287,14 +286,6 @@ public class ApplicationProperties {
this.autoCreateUser = autoCreateUser;
}
public Boolean getBlockRegistration() {
return blockRegistration;
}
public void setBlockRegistration(Boolean blockRegistration) {
this.blockRegistration = blockRegistration;
}
public String getUseAsUsername() {
return useAsUsername;
}
@@ -365,8 +356,6 @@ public class ApplicationProperties {
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
+ ", autoCreateUser="
+ autoCreateUser
+ ", blockRegistration="
+ blockRegistration
+ ", useAsUsername="
+ useAsUsername
+ ", provider="
@@ -442,15 +431,6 @@ public class ApplicationProperties {
private boolean showUpdate;
private Boolean showUpdateOnlyAdmin;
private boolean customHTMLFiles;
private String tessdataDir;
public String getTessdataDir() {
return tessdataDir;
}
public void setTessdataDir(String tessdataDir) {
this.tessdataDir = tessdataDir;
}
public boolean isCustomHTMLFiles() {
return customHTMLFiles;

View File

@@ -1,7 +1,5 @@
package stirling.software.SPDF.model;
import java.io.Serializable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
@@ -13,9 +11,7 @@ import jakarta.persistence.Table;
@Entity
@Table(name = "authorities")
public class Authority implements Serializable {
private static final long serialVersionUID = 1L;
public class Authority {
public Authority() {}

View File

@@ -1,23 +0,0 @@
package stirling.software.SPDF.model;
import java.io.Serializable;
import java.util.Date;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Lob;
import jakarta.persistence.Table;
import lombok.Data;
@Entity
@Data
@Table(name = "sessions")
public class SessionEntity implements Serializable {
@Id private String sessionId;
@Lob private String principalName;
private Date lastRequest;
private boolean expired;
}

View File

@@ -1,6 +1,5 @@
package stirling.software.SPDF.model;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -24,9 +23,7 @@ import jakarta.persistence.Table;
@Entity
@Table(name = "users")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@@ -12,7 +12,7 @@ public class ConvertToImageRequest extends PDFFile {
@Schema(
description = "The output image format",
allowableValues = {"png", "jpeg", "jpg", "gif", "webp"})
allowableValues = {"png", "jpeg", "jpg", "gif"})
private String imageFormat;
@Schema(

View File

@@ -9,8 +9,6 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class UrlToPdfRequest {
@Schema(
description = "The input URL to be converted to a PDF file",
requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "The input URL to be converted to a PDF file", required = true)
private String urlInput;
}

View File

@@ -10,6 +10,6 @@ import stirling.software.SPDF.model.api.PDFWithPageNums;
@EqualsAndHashCode(callSuper = true)
public class ContainsTextRequest extends PDFWithPageNums {
@Schema(description = "The text to check for", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "The text to check for", required = true)
private String text;
}

View File

@@ -10,6 +10,6 @@ import stirling.software.SPDF.model.api.PDFComparison;
@EqualsAndHashCode(callSuper = true)
public class FileSizeRequest extends PDFComparison {
@Schema(description = "File Size", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "File Size", required = true)
private String fileSize;
}

View File

@@ -10,6 +10,6 @@ import stirling.software.SPDF.model.api.PDFComparison;
@EqualsAndHashCode(callSuper = true)
public class PageRotationRequest extends PDFComparison {
@Schema(description = "Rotation in degrees", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "Rotation in degrees", required = true)
private int rotation;
}

View File

@@ -10,6 +10,6 @@ import stirling.software.SPDF.model.api.PDFComparison;
@EqualsAndHashCode(callSuper = true)
public class PageSizeRequest extends PDFComparison {
@Schema(description = "Standard Page Size", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "Standard Page Size", required = true)
private String standardPageSize;
}

View File

@@ -20,13 +20,13 @@ public class OverlayPdfsRequest extends PDFFile {
@Schema(
description =
"The mode of overlaying: 'SequentialOverlay' for sequential application, 'InterleavedOverlay' for round-robin application, 'FixedRepeatOverlay' for fixed repetition based on provided counts",
requiredMode = Schema.RequiredMode.REQUIRED)
required = true)
private String overlayMode;
@Schema(
description =
"An array of integers specifying the number of times each corresponding overlay file should be applied in the 'FixedRepeatOverlay' mode. This should match the length of the overlayFiles array.",
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
required = false)
private int[] counts;
@Schema(description = "Overlay position 0 is Foregound, 1 is Background")

View File

@@ -13,14 +13,14 @@ public class SplitPdfBySizeOrCountRequest extends PDFFile {
@Schema(
description =
"Determines the type of split: 0 for size, 1 for page count, 2 for document count",
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
required = false,
defaultValue = "0")
private int splitType;
@Schema(
description =
"Value for split: size in MB (e.g., '10MB') or number of pages (e.g., '5')",
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
required = false,
defaultValue = "10MB")
private String splitValue;
}

View File

@@ -15,7 +15,7 @@ public class AddStampRequest extends PDFWithPageNums {
@Schema(
description = "The stamp type (text or image)",
allowableValues = {"text", "image"},
requiredMode = Schema.RequiredMode.REQUIRED)
required = true)
private String stampType;
@Schema(description = "The stamp text")

View File

@@ -13,7 +13,7 @@ public class AutoSplitPdfRequest extends PDFFile {
@Schema(
description =
"Flag indicating if the duplex mode is active, where the page after the divider also gets removed.",
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
required = false,
defaultValue = "false")
private boolean duplexMode;
}

View File

@@ -13,7 +13,7 @@ public class ExtractHeaderRequest extends PDFFile {
@Schema(
description =
"Flag indicating whether to use the first text as a fallback if no suitable title is found. Defaults to false.",
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
required = false,
defaultValue = "false")
private boolean useFirstTextAsFallback;
}

View File

@@ -10,9 +10,7 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode
public class ExtractImageScansRequest {
@Schema(
description = "The input file containing image scans",
requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "The input file containing image scans", required = true)
private MultipartFile fileInput;
@Schema(

View File

@@ -10,8 +10,6 @@ import stirling.software.SPDF.model.api.PDFFile;
@EqualsAndHashCode(callSuper = true)
public class PrintFileRequest extends PDFFile {
@Schema(
description = "Name of printer to match against",
requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "Name of printer to match against", required = true)
private String printerName;
}

View File

@@ -15,7 +15,7 @@ public class AddWatermarkRequest extends PDFFile {
@Schema(
description = "The watermark type (text or image)",
allowableValues = {"text", "image"},
requiredMode = Schema.RequiredMode.REQUIRED)
required = true)
private String watermarkType;
@Schema(description = "The watermark text")
@@ -44,7 +44,4 @@ public class AddWatermarkRequest extends PDFFile {
@Schema(description = "The height spacer between watermark elements", example = "50")
private int heightSpacer;
@Schema(description = "Convert the redacted PDF to an image", defaultValue = "false")
private boolean convertPDFToImage;
}

View File

@@ -10,8 +10,6 @@ import stirling.software.SPDF.model.api.PDFFile;
@EqualsAndHashCode(callSuper = true)
public class PDFPasswordRequest extends PDFFile {
@Schema(
description = "The password of the PDF file",
requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "The password of the PDF file", required = true)
private String password;
}

View File

@@ -10,10 +10,7 @@ import stirling.software.SPDF.model.api.PDFFile;
@EqualsAndHashCode(callSuper = true)
public class RedactPdfRequest extends PDFFile {
@Schema(
description = "List of text to redact from the PDF",
type = "string",
requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "List of text to redact from the PDF", type = "string", required = true)
private String listOfText;
@Schema(description = "Whether to use regex for the listOfText", defaultValue = "false")

View File

@@ -31,14 +31,6 @@ public class GoogleProvider extends Provider {
private Collection<String> scopes = new ArrayList<>();
private String useAsUsername = "email";
@Override
public String getIssuer() {
return new String();
}
@Override
public void setIssuer(String issuer) {}
@Override
public String getClientId() {
return this.clientId;

View File

@@ -13,5 +13,5 @@ public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByApiKey(String apiKey);
User findByApiKey(String apiKey);
}

View File

@@ -1,51 +0,0 @@
package stirling.software.SPDF.service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.springframework.stereotype.Service;
/** Service class responsible for removing image objects from a PDF document. */
@Service
public class PdfImageRemovalService {
/**
* Removes all image objects from the provided PDF document.
*
* <p>This method iterates over each page in the document and removes any image XObjects found
* in the page's resources.
*
* @param document The PDF document from which images will be removed.
* @return The modified PDF document with images removed.
* @throws IOException If an error occurs while processing the PDF document.
*/
public PDDocument removeImagesFromPdf(PDDocument document) throws IOException {
// Iterate over each page in the PDF document
for (PDPage page : document.getPages()) {
PDResources resources = page.getResources();
// Collect the XObject names to remove
List<COSName> namesToRemove = new ArrayList<>();
// Iterate over all XObject names in the page's resources
for (COSName name : resources.getXObjectNames()) {
// Check if the XObject is an image
if (resources.isImageXObject(name)) {
// Collect the name for removal
namesToRemove.add(name);
}
}
// Now, modify the resources by removing the collected names
for (COSName name : namesToRemove) {
resources.put(name, (PDXObject) null);
}
}
return document;
}
}

View File

@@ -1,59 +0,0 @@
package stirling.software.SPDF.utils;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
public class CheckProgramInstall {
private static final List<String> PYTHON_COMMANDS = Arrays.asList("python3", "python");
private static boolean pythonAvailableChecked = false;
private static String availablePythonCommand = null;
/**
* Checks which Python command is available and returns it.
*
* @return The available Python command ("python3" or "python"), or null if neither is
* available.
*/
public static String getAvailablePythonCommand() {
if (!pythonAvailableChecked) {
availablePythonCommand =
PYTHON_COMMANDS.stream()
.filter(CheckProgramInstall::checkPythonVersion)
.findFirst()
.orElse(null);
pythonAvailableChecked = true;
}
return availablePythonCommand;
}
/**
* Checks if the specified command is available by running the command with --version.
*
* @param pythonCommand The Python command to check.
* @return true if the command is available, false otherwise.
*/
private static boolean checkPythonVersion(String pythonCommand) {
try {
ProcessExecutorResult result =
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
.runCommandWithOutputHandling(
Arrays.asList(pythonCommand, "--version"));
return true; // Command succeeded, Python is available
} catch (IOException | InterruptedException e) {
return false; // Command failed, Python is not available
}
}
/**
* Checks if any Python command is available.
*
* @return true if any Python command is available, false otherwise.
*/
public static boolean isPythonAvailable() {
return getAvailablePythonCommand() != null;
}
}

View File

@@ -4,10 +4,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -76,20 +73,6 @@ public class GeneralUtils {
}
}
public static boolean isURLReachable(String urlStr) {
try {
URL url = URI.create(urlStr).toURL();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("HEAD");
int responseCode = connection.getResponseCode();
return (200 <= responseCode && responseCode <= 399);
} catch (MalformedURLException e) {
return false;
} catch (IOException e) {
return false;
}
}
public static File multipartToFile(MultipartFile multipart) throws IOException {
Path tempFile = Files.createTempFile("overlay-", ".pdf");
try (InputStream in = multipart.getInputStream();
@@ -187,8 +170,7 @@ public class GeneralUtils {
int n = 0;
while (true) {
// Replace 'n' with the current value of n, correctly handling numbers before
// 'n'
// Replace 'n' with the current value of n, correctly handling numbers before 'n'
String sanitizedExpression = insertMultiplicationBeforeN(expression, n);
Double result = evaluator.evaluate(sanitizedExpression);

View File

@@ -341,30 +341,6 @@ public class PdfUtils {
}
}
/**
* Converts a given Pdf file to PDF-Image.
*
* @param document to be converted. Note: the caller is responsible for closing the document
* @return converted document to PDF-Image
* @throws IOException if conversion fails
*/
public static PDDocument convertPdfToPdfImage(PDDocument document) throws IOException {
PDDocument imageDocument = new PDDocument();
PDFRenderer pdfRenderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true);
for (int page = 0; page < document.getNumberOfPages(); ++page) {
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
PDPage newPage = new PDPage(new PDRectangle(bim.getWidth(), bim.getHeight()));
imageDocument.addPage(newPage);
PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim);
PDPageContentStream contentStream =
new PDPageContentStream(imageDocument, newPage, AppendMode.APPEND, true, true);
contentStream.drawImage(pdImage, 0, 0);
contentStream.close();
}
return imageDocument;
}
private static BufferedImage prepareImageForPdfToImage(
int maxWidth, int height, String imageType) {
BufferedImage combined;

View File

@@ -1,11 +1,5 @@
multipart.enabled=true
logging.level.org.springframework=WARN
logging.level.org.hibernate=WARN
logging.level.org.eclipse.jetty=WARN
logging.level.com.zaxxer.hikari=WARN
spring.jpa.open-in-view=false
server.forward-headers-strategy=NATIVE
@@ -30,8 +24,10 @@ spring.devtools.livereload.enabled=true
spring.thymeleaf.encoding=UTF-8
server.connection-timeout=${SYSTEM_CONNECTIONTIMEOUTMINUTES:20m}
spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:1200000}
spring.resources.static-locations=file:customFiles/static/
#spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/
#spring.thymeleaf.cache=false

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=User not found.
incorrectPasswordMessage=Current password is incorrect.
usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
downgradeCurrentUserMessage=لا يمكن خفض دور المستخدم الحالي
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=لا يمكن تخفيض دور المستخدم الحالي. وبالتالي، لن يظهر المستخدم الحالي.
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
userAlreadyExistsWebMessage=The user already exists as an web user.
@@ -179,7 +177,6 @@ adminUserSettings.user=User
adminUserSettings.addUser=Add New User
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
adminUserSettings.roles=Roles
adminUserSettings.role=Role
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User
adminUserSettings.changeUserRole=تغيير دور المستخدم
adminUserSettings.authenticated=Authenticated
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Your account has been locked.
login.signinTitle=Please sign in
login.ssoSignIn=تسجيل الدخول عبر تسجيل الدخول الأحادي
login.oauth2AutoCreateDisabled=تم تعطيل مستخدم الإنشاء التلقائي لـ OAuth2
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Authorization request not found
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=الحد الأدنى لمنطقة المحيط:
ScannerImageSplit.selectText.8=تعيين الحد الأدنى لمنطقة المحيط للصورة
ScannerImageSplit.selectText.9=حجم الحدود:
ScannerImageSplit.selectText.10=يضبط حجم الحدود المضافة والمزالة لمنع الحدود البيضاء في الإخراج (الافتراضي: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=اللون
pdfToImage.grey=تدرج الرمادي
pdfToImage.blackwhite=أبيض وأسود (قد يفقد البيانات!)
pdfToImage.submit=تحول
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=heightSpacer (مسافة بين كل علامة مائي
watermark.selectText.7=التعتيم (0٪ - 100٪):
watermark.selectText.8=Watermark Type:
watermark.selectText.9=Watermark Image:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=إضافة علامة مائية
watermark.type.1=نص
watermark.type.2=صورة
@@ -1143,10 +1124,3 @@ error.showStack=Show Stack Trace
error.copyStack=Copy Stack Trace
error.githubSubmit=GitHub - Submit a ticket
error.discordSubmit=Discord - Submit Support post
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=Потребителят не е намерен
incorrectPasswordMessage=Текущата парола е неправилна.
usernameExistsMessage=Новият потребител вече съществува.
invalidUsernameMessage=Невалидно потребителско име, потребителското име може да съдържа само букви, цифри и следните специални знаци @._+- или трябва да е валиден имейл адрес.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Не може да се изтрие вписания в момента потребител.
deleteUsernameExistsMessage=Потребителското име не съществува и не може да бъде изтрито.
downgradeCurrentUserMessage=Не може да се понижи ролята на текущия потребител
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=Не може да се понижи ролята на текущия потребител. Следователно текущият потребител няма да бъде показан.
userAlreadyExistsOAuthMessage=Потребителят вече съществува като OAuth2 потребител.
userAlreadyExistsWebMessage=Потребителят вече съществува като уеб-потребител.
@@ -179,7 +177,6 @@ adminUserSettings.user=Потребител
adminUserSettings.addUser=Добавяне на нов потребител
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Потребителското име може да съдържа само букви, цифри и следните специални символи @._+- или трябва да е валиден имейл адрес.
adminUserSettings.roles=Роли
adminUserSettings.role=Роля
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Принудете потребителя да п
adminUserSettings.submit=Съхранете потребителя
adminUserSettings.changeUserRole=Промяна на ролята на потребителя
adminUserSettings.authenticated=Удостоверен
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Книга към PDF
home.BookToPDF.desc=Преобразува формати на книги/комикси в PDF с помощта на calibre
BookToPDF.tags=Книга,комикс,calibre,конвертиране,манга,Amazon,Kindle
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Вашият акаунт е заключен.
login.signinTitle=Моля впишете се
login.ssoSignIn=Влизане чрез еднократно влизане
login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Заявката за оторизация не е намерена
login.oauth2InvalidUserInfoResponse=Невалидна информация за потребителя
login.oauth2invalidRequest=Невалидна заявка
login.oauth2AccessDenied=Отказан достъп
login.oauth2InvalidTokenResponse=Невалиден отговор на токена
login.oauth2InvalidIdToken=Невалиден токен за идентификатор
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Минимална контурна площ:
ScannerImageSplit.selectText.8=Задава минималния праг на контурната площ за изображение
ScannerImageSplit.selectText.9=Размер на рамката:
ScannerImageSplit.selectText.10=Задава размера на добавената и премахната граница, за да предотврати бели граници към изхода (по подразбиране: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Цвят
pdfToImage.grey=Скала на сивото
pdfToImage.blackwhite=Черно и бяло (може да загубите данни!)
pdfToImage.submit=Преобразуване
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=дължинаSpacer (Разстояние между в
watermark.selectText.7=Непрозрачност (0% - 100%):
watermark.selectText.8=Тип воден знак:
watermark.selectText.9=Изображение за воден знак:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Добавяне на воден знак
watermark.type.1=Текст
watermark.type.2=Изображение
@@ -1143,10 +1124,3 @@ error.showStack=Покажи проследяване на стека
error.copyStack=Копиране на проследяване на стека
error.githubSubmit=GitHub - Изпратете запитване
error.discordSubmit=Discord - Изпратете запитване за поддръжка
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=User not found.
incorrectPasswordMessage=Current password is incorrect.
usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
downgradeCurrentUserMessage=No es pot reduir la funció de l'usuari actual
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=No es pot baixar la funció de l'usuari actual. Per tant, no es mostrarà l'usuari actual.
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
userAlreadyExistsWebMessage=The user already exists as an web user.
@@ -179,7 +177,6 @@ adminUserSettings.user=Usuari
adminUserSettings.addUser=Afegir Usuari
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
adminUserSettings.roles=Rols
adminUserSettings.role=Rol
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Desar Usuari
adminUserSettings.changeUserRole=Canvia el rol de l'usuari
adminUserSettings.authenticated=Authenticated
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Compte bloquejat
login.signinTitle=Autenticat
login.ssoSignIn=Inicia sessió mitjançant l'inici de sessió ún
login.oauth2AutoCreateDisabled=L'usuari de creació automàtica OAUTH2 està desactivat
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Authorization request not found
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Àrea de contorn mínima:
ScannerImageSplit.selectText.8=Estableix el llindar mínim de l'àrea de contorn per a una foto
ScannerImageSplit.selectText.9=Mida Vora:
ScannerImageSplit.selectText.10=Estableix la mida de la vora afegida i eliminada per evitar vores blanques a la sortida (per defecte: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Color
pdfToImage.grey=Escala de Grisos
pdfToImage.blackwhite=Blanc i Negre (Pot perdre dades!)
pdfToImage.submit=Converteix
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=separació d'alçada (Espai vertical entre cada Marca d'A
watermark.selectText.7=Opacitat (0% - 100%):
watermark.selectText.8=Watermark Type:
watermark.selectText.9=Watermark Image:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Afegir Marca d'Aigua
watermark.type.1=Text
watermark.type.2=Image
@@ -1143,10 +1124,3 @@ error.showStack=Show Stack Trace
error.copyStack=Copy Stack Trace
error.githubSubmit=GitHub - Submit a ticket
error.discordSubmit=Discord - Submit Support post
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=Uživatel nenalezen.
incorrectPasswordMessage=Současné heslo není správné.
usernameExistsMessage=Nové uživatelské jméno již existuje.
invalidUsernameMessage=Nesprávné uživatelské jméno, smí obsahovat pouze písmena, číslice a následující speciální znaky @._+- nebo musí být validní emailová adresa.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Nelze smazat aktuální přihlášeného uživatele.
deleteUsernameExistsMessage=Uživatelské jméno neexistuje a nelze ho smazat.
downgradeCurrentUserMessage=Nelze snížit roli aktuálního uživatele.
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=Nelze snížit roli aktuálního uživatele. Proto nebude aktuální uživatel zobrazen.
userAlreadyExistsOAuthMessage=Uživatel již existuje jako OAuth2 uživatel.
userAlreadyExistsWebMessage=Uživatel již existuje jako webový uživatel.
@@ -179,7 +177,6 @@ adminUserSettings.user=Uživatel
adminUserSettings.addUser=Přidat Nového Uživatele
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Uživatelské Jméno může obsahovat pouze písmena, čísla a následující speciální znaky @._+- nebo musí být správná emailová adresa.
adminUserSettings.roles=Role
adminUserSettings.role=Role
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Vynutit uživateli změnu hesla při přihlášen
adminUserSettings.submit=Uložit Uživatele
adminUserSettings.changeUserRole=Zmenit Roli Uživatele
adminUserSettings.authenticated=Ověřeno
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Kniha na PDF
home.BookToPDF.desc=Převádí formáty knih/komiksů do PDF pomocí calibre
BookToPDF.tags=Kniha,Komiks,Calibre,Konvertovat,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Your account has been locked.
login.signinTitle=Please sign in
login.ssoSignIn=Login via Single Sign-on
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Authorization request not found
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Minimální plocha kontury:
ScannerImageSplit.selectText.8=Nastaví minimální plošný práh kontury pro fotografii
ScannerImageSplit.selectText.9=Velikost okraje:
ScannerImageSplit.selectText.10=Nastaví velikost okraje přidaného a odebraného k zabránění bílých ohraničení ve výstupu (výchozí: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Barevný
pdfToImage.grey=Stupně šedi
pdfToImage.blackwhite=Černobílý (Může dojít k ztrátě dat!)
pdfToImage.submit=Převést
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=Výška mezery (Mezera mezi každým vodoznakem svisle):
watermark.selectText.7=Průhlednost (0% - 100%):
watermark.selectText.8=Typ vodoznaku:
watermark.selectText.9=Obrázek vodoznaku:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Přidat vodoznak
watermark.type.1=Text
watermark.type.2=Obrázek
@@ -1143,10 +1124,3 @@ error.showStack=Zobrazit stopu zásobníku
error.copyStack=Kopírovat stopu zásobníku
error.githubSubmit=GitHub - Odeslat požadavek
error.discordSubmit=Discord - Odeslat příspěvek podpory
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=Bruger ikke fundet.
incorrectPasswordMessage=Nuværende adgangskode er forkert.
usernameExistsMessage=Nyt brugernavn findes allerede.
invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
downgradeCurrentUserMessage=Cannot downgrade current user's role
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=Cannot downgrade current user's role. Hence, current user will not be shown.
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
userAlreadyExistsWebMessage=The user already exists as an web user.
@@ -179,7 +177,6 @@ adminUserSettings.user=User
adminUserSettings.addUser=Add New User
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
adminUserSettings.roles=Roles
adminUserSettings.role=Role
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User
adminUserSettings.changeUserRole=Change User's Role
adminUserSettings.authenticated=Authenticated
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Your account has been locked.
login.signinTitle=Please sign in
login.ssoSignIn=Login via Single Sign-on
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Authorization request not found
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Minimum Contour Area:
ScannerImageSplit.selectText.8=Sets the minimum contour area threshold for a photo
ScannerImageSplit.selectText.9=Border Size:
ScannerImageSplit.selectText.10=Sets the size of the border added and removed to prevent white borders in the output (default: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Colour
pdfToImage.grey=Greyscale
pdfToImage.blackwhite=Black and White (May lose data!)
pdfToImage.submit=Convert
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=heightSpacer (Space between each watermark vertically):
watermark.selectText.7=Opacity (0% - 100%):
watermark.selectText.8=Watermark Type:
watermark.selectText.9=Watermark Image:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Add Watermark
watermark.type.1=Text
watermark.type.2=Image
@@ -1143,10 +1124,3 @@ error.showStack=Vis Stack Trace
error.copyStack=Kopier Stack Trace
error.githubSubmit=GitHub - Indsend en ticket
error.discordSubmit=Discord - Indsend Support post
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=Benutzer nicht gefunden.
incorrectPasswordMessage=Das Passwort ist falsch.
usernameExistsMessage=Neuer Benutzername existiert bereits.
invalidUsernameMessage=Ungültiger Benutzername. Der Benutzername darf nur Buchstaben, Zahlen und die folgenden Sonderzeichen @._+- enthalten oder muss eine gültige E-Mail-Adresse sein.
invalidPasswordMessage=Das Passwort darf nicht leer sein und kein Leerzeichen am Anfang und Ende haben.
confirmPasswordErrorMessage=„Neues Passwort“ und „Neues Passwort bestätigen“ müssen übereinstimmen.
deleteCurrentUserMessage=Der aktuell angemeldete Benutzer kann nicht gelöscht werden.
deleteUsernameExistsMessage=Der Benutzername existiert nicht und kann nicht gelöscht werden.
downgradeCurrentUserMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden
disabledCurrentUserMessage=Der aktuelle Benutzer kann nicht deaktiviert werden
downgradeCurrentUserLongMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden. Daher wird der aktuelle Benutzer nicht angezeigt.
userAlreadyExistsOAuthMessage=Der Benutzer ist bereits als OAuth2-Benutzer vorhanden.
userAlreadyExistsWebMessage=Der Benutzer ist bereits als Webbenutzer vorhanden.
@@ -179,11 +177,10 @@ adminUserSettings.user=Benutzer
adminUserSettings.addUser=Neuen Benutzer hinzufügen
adminUserSettings.deleteUser=Benutzer löschen
adminUserSettings.confirmDeleteUser=Soll der Benutzer gelöscht werden?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Der Benutzername darf nur Buchstaben, Zahlen und die folgenden Sonderzeichen @._+- enthalten oder muss eine gültige E-Mail-Adresse sein.
adminUserSettings.roles=Rollen
adminUserSettings.role=Rolle
adminUserSettings.actions=Aktions
adminUserSettings.actions=Aktion
adminUserSettings.apiUser=Eingeschränkter API-Benutzer
adminUserSettings.extraApiUser=Zusätzlicher eingeschränkter API-Benutzer
adminUserSettings.webOnlyUser=Nur Web-Benutzer
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei
adminUserSettings.submit=Benutzer speichern
adminUserSettings.changeUserRole=Benutzerrolle ändern
adminUserSettings.authenticated=Authentifiziert
adminUserSettings.editOwnProfil=Eigenes Profil bearbeiten
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Datenbank Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Buch als PDF
home.BookToPDF.desc=Konvertiert Buch-/Comic-Formate mithilfe von Calibre in PDF
BookToPDF.tags=buch,comic,calibre,convert,manga,amazon,kindle
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Ihr Konto wurde gesperrt.
login.signinTitle=Bitte melden Sie sich an.
login.ssoSignIn=Anmeldung per Single Sign-On
login.oauth2AutoCreateDisabled=OAUTH2 Benutzer automatisch erstellen deaktiviert
login.oauth2AdminBlockedUser=Die Registrierung bzw. das anmelden von nicht registrierten Benutzern ist derzeit gesperrt. Bitte wenden Sie sich an den Administrator.
login.oauth2RequestNotFound=Autorisierungsanfrage nicht gefunden
login.oauth2InvalidUserInfoResponse=Ungültige Benutzerinformationsantwort
login.oauth2invalidRequest=ungültige Anfrage
login.oauth2AccessDenied=Zugriff abgelehnt
login.oauth2InvalidTokenResponse=Ungültige Token-Antwort
login.oauth2InvalidIdToken=Ungültiges ID-Token
login.userIsDisabled=Benutzer ist deaktiviert, die Anmeldung ist mit diesem Benutzernamen derzeit gesperrt. Bitte wenden Sie sich an den Administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Minimaler Konturbereich:
ScannerImageSplit.selectText.8=Legt den minimalen Konturbereichsschwellenwert für ein Foto fest
ScannerImageSplit.selectText.9=Randgröße:
ScannerImageSplit.selectText.10=Legt die Größe des hinzugefügten und entfernten Randes fest, um weiße Ränder in der Ausgabe zu verhindern (Standard: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Farbe
pdfToImage.grey=Graustufen
pdfToImage.blackwhite=Schwarzweiß (Datenverlust möglich!)
pdfToImage.submit=Umwandeln
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=höheSpacer (vertikaler Abstand zwischen den einzelnen Wa
watermark.selectText.7=Deckkraft (0% - 100 %):
watermark.selectText.8=Wasserzeichen Typ:
watermark.selectText.9=Wasserzeichen-Bild:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Wasserzeichen hinzufügen
watermark.type.1=Text
watermark.type.2=Bild
@@ -1143,10 +1124,3 @@ error.showStack=Stack-Trace anzeigen
error.copyStack=Stack-Trace kopieren
error.githubSubmit=GitHub - Ein Ticket einreichen
error.discordSubmit=Discord - Unterstützungsbeitrag einreichen
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=Ο χρήστης δεν βρέθηκε.
incorrectPasswordMessage=Ο τρέχων κωδικός πρόσβασης είναι λανθασμένος.
usernameExistsMessage=Το νέο όνομα χρήστη υπάρχει ήδη.
invalidUsernameMessage=Μη έγκυρο όνομα χρήστη, όνομα χρήστη μπορεί να περιέχει μόνο γράμματα, αριθμούς και τους ακόλουθους ειδικούς χαρακτήρες @._+- ή πρέπει να είναι έγκυρη διεύθυνση email.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Δεν είναι δυνατή η διαγραφή του τρέχοντος συνδεδεμένου χρήστη.
deleteUsernameExistsMessage=Το όνομα χρήστη δεν υπάρχει και δεν μπορεί να διαγραφεί.
downgradeCurrentUserMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη. Ως εκ τούτου, ο τρέχων χρήστης δεν θα εμφανίζεται.
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
userAlreadyExistsWebMessage=The user already exists as an web user.
@@ -179,7 +177,6 @@ adminUserSettings.user=Χρήστης
adminUserSettings.addUser=Προσθήκη νέου Χρήστη
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
adminUserSettings.roles=Ρόλοι
adminUserSettings.role=Ρόλος
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Αναγκάστε τον χρήστη να αλλ
adminUserSettings.submit=Αποθήκευση Χρήστη
adminUserSettings.changeUserRole=Αλλαγή ρόλου χρήστη
adminUserSettings.authenticated=Authenticated
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Book σε PDF
home.BookToPDF.desc=Μετατρέπει τις μορφές Books/Comics σε PDF χρησιμοποιώντας calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Ο λογαριασμός σας έχει κλειδωθεί.
login.signinTitle=Παρακαλώ, συνδεθείτε
login.ssoSignIn=Σύνδεση μέσω μοναδικής σύνδεσης
login.oauth2AutoCreateDisabled=Απενεργοποιήθηκε ο χρήστης αυτόματης δημιουργίας OAUTH2
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Authorization request not found
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Ελάχιστη επιφάνεια περιγρ
ScannerImageSplit.selectText.8=Ρυθμίζει το ελάχιστο όριο περιγράμματος για μια φωτογραφία
ScannerImageSplit.selectText.9=Μέγεθος περιγράμματος:
ScannerImageSplit.selectText.10=Ορίζει το μέγεθος του περιγράμματος που προστίθεται και αφαιρείται για να αποτρέπονται λευκά περιγράμματα στην έξοδο (προεπιλογή: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Χρώμα
pdfToImage.grey=Κλίμακα του γκρι
pdfToImage.blackwhite=Ασπρόμαυρο (Μπορεί να χαθούν δεδομένα!)
pdfToImage.submit=Μετατροπή
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=heightSpacer (Κενό μεταξύ κάθε υδατογ
watermark.selectText.7=Αδιαφάνεια (Opacity) (0% - 100%):
watermark.selectText.8=Τύπος Υδατογραφήματος:
watermark.selectText.9=Εικόνα Υδατογραφήματος:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Προσθήκη Υδατογραφήματος
watermark.type.1=Κείμενο
watermark.type.2=Εικόνα
@@ -1143,10 +1124,3 @@ error.showStack=Εμφάνιση Stack Trace
error.copyStack=Αντιγραφή Stack Trace
error.githubSubmit=GitHub - Υποβάλετε ένα ticket
error.discordSubmit=Discord - Υποβάλετε ένα Support post
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=User not found.
incorrectPasswordMessage=Current password is incorrect.
usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
downgradeCurrentUserMessage=Cannot downgrade current user's role
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=Cannot downgrade current user's role. Hence, current user will not be shown.
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
userAlreadyExistsWebMessage=The user already exists as an web user.
@@ -179,7 +177,6 @@ adminUserSettings.user=User
adminUserSettings.addUser=Add New User
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
adminUserSettings.roles=Roles
adminUserSettings.role=Role
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User
adminUserSettings.changeUserRole=Change User's Role
adminUserSettings.authenticated=Authenticated
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Your account has been locked.
login.signinTitle=Please sign in
login.ssoSignIn=Login via Single Sign-on
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Authorization request not found
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Minimum Contour Area:
ScannerImageSplit.selectText.8=Sets the minimum contour area threshold for a photo
ScannerImageSplit.selectText.9=Border Size:
ScannerImageSplit.selectText.10=Sets the size of the border added and removed to prevent white borders in the output (default: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Colour
pdfToImage.grey=Greyscale
pdfToImage.blackwhite=Black and White (May lose data!)
pdfToImage.submit=Convert
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=heightSpacer (Space between each watermark vertically):
watermark.selectText.7=Opacity (0% - 100%):
watermark.selectText.8=Watermark Type:
watermark.selectText.9=Watermark Image:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Add Watermark
watermark.type.1=Text
watermark.type.2=Image
@@ -1143,10 +1124,3 @@ error.showStack=Show Stack Trace
error.copyStack=Copy Stack Trace
error.githubSubmit=GitHub - Submit a ticket
error.discordSubmit=Discord - Submit Support post
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=User not found.
incorrectPasswordMessage=Current password is incorrect.
usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
downgradeCurrentUserMessage=Cannot downgrade current user's role
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=Cannot downgrade current user's role. Hence, current user will not be shown.
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
userAlreadyExistsWebMessage=The user already exists as an web user.
@@ -179,7 +177,6 @@ adminUserSettings.user=User
adminUserSettings.addUser=Add New User
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
adminUserSettings.roles=Roles
adminUserSettings.role=Role
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User
adminUserSettings.changeUserRole=Change User's Role
adminUserSettings.authenticated=Authenticated
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Your account has been locked.
login.signinTitle=Please sign in
login.ssoSignIn=Login via Single Sign-on
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Authorization request not found
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Minimum Contour Area:
ScannerImageSplit.selectText.8=Sets the minimum contour area threshold for a photo
ScannerImageSplit.selectText.9=Border Size:
ScannerImageSplit.selectText.10=Sets the size of the border added and removed to prevent white borders in the output (default: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Color
pdfToImage.grey=Grayscale
pdfToImage.blackwhite=Black and White (May lose data!)
pdfToImage.submit=Convert
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=heightSpacer (Space between each watermark vertically):
watermark.selectText.7=Opacity (0% - 100%):
watermark.selectText.8=Watermark Type:
watermark.selectText.9=Watermark Image:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Add Watermark
watermark.type.1=Text
watermark.type.2=Image
@@ -1143,10 +1124,3 @@ error.showStack=Show Stack Trace
error.copyStack=Copy Stack Trace
error.githubSubmit=GitHub - Submit a ticket
error.discordSubmit=Discord - Submit Support post
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=Usuario no encontrado.
incorrectPasswordMessage=La contraseña actual no es correcta.
usernameExistsMessage=El nuevo nombre de usuario está en uso.
invalidUsernameMessage=Nombre de usuario no válido, el nombre de usuario solo puede contener letras, números y los siguientes caracteres especiales @._+- o debe ser una dirección de correo electrónico válida.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=No puede eliminar el usuario que tiene la sesión actualmente en uso.
deleteUsernameExistsMessage=El usuario no existe y no puede eliminarse.
downgradeCurrentUserMessage=No se puede degradar el rol del usuario actual
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=No se puede degradar el rol del usuario actual. Por lo tanto, el usuario actual no se mostrará.
userAlreadyExistsOAuthMessage=La usuario ya existe como usuario de OAuth2.
userAlreadyExistsWebMessage=El usuario ya existe como usuario web.
@@ -179,7 +177,6 @@ adminUserSettings.user=Usuario
adminUserSettings.addUser=Añadir Nuevo Usuario
adminUserSettings.deleteUser=Eliminar Usuario
adminUserSettings.confirmDeleteUser=¿Se debe eliminar al usuario?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=El nombre de usuario solo puede contener letras, números y los siguientes caracteres especiales @._+- o debe ser una dirección de correo electrónico válida.
adminUserSettings.roles=Roles
adminUserSettings.role=Rol
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Forzar usuario a cambiar usuario/contraseña en el
adminUserSettings.submit=Guardar Usuario
adminUserSettings.changeUserRole=Cambiar rol de usuario
adminUserSettings.authenticated=Autenticado
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Base de Datos Importar/Exportar
@@ -471,10 +461,6 @@ home.BookToPDF.title=Libro a PDF
home.BookToPDF.desc=Convierte formatos de Libro/Cómic a PDF usando Calibre
BookToPDF.tags=Libro,Cómic,Calibre,Convertir,manga,Amazon,Kindle
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Su cuenta se ha bloqueado.
login.signinTitle=Por favor, inicie sesión
login.ssoSignIn=Iniciar sesión a través del inicio de sesión único
login.oauth2AutoCreateDisabled=Usuario de creación automática de OAUTH2 DESACTIVADO
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Solicitud de autorización no encontrada
login.oauth2InvalidUserInfoResponse=Respuesta de información de usuario no válida
login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Respuesta de token no válida
login.oauth2InvalidIdToken=Token de identificación no válido
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Área mínima de contorno:
ScannerImageSplit.selectText.8=Establecer el umbral mínimo del área de contorno para una foto
ScannerImageSplit.selectText.9=Tamaño del borde:
ScannerImageSplit.selectText.10=Establece el tamaño del borde agregado y eliminado para evitar bordes blancos en la salida (predeterminado: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Color
pdfToImage.grey=Escala de grises
pdfToImage.blackwhite=Blanco y Negro (¡Puede perder datos!)
pdfToImage.submit=Convertir
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=Alto (Espacio entre cada marca de agua verticalmente):
watermark.selectText.7=Opacidad (0% - 100%):
watermark.selectText.8=Tipo de marca de agua:
watermark.selectText.9=Imagen de marca de agua:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Añadir marca de agua
watermark.type.1=Texto
watermark.type.2=Imagen
@@ -1143,10 +1124,3 @@ error.showStack=Mostrar seguimiento de pila
error.copyStack=Mostrar seguimiento de pila
error.githubSubmit=GitHub - Enviar un ticket
error.discordSubmit=Discord - Enviar mensaje de soporte
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=User not found.
incorrectPasswordMessage=Current password is incorrect.
usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
downgradeCurrentUserMessage=Ezin da uneko erabiltzailearen rola jaitsi
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=Ezin da uneko erabiltzailearen rola jaitsi. Beraz, oraingo erabiltzailea ez da erakutsiko.
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
userAlreadyExistsWebMessage=The user already exists as an web user.
@@ -179,7 +177,6 @@ adminUserSettings.user=Erabiltzaile
adminUserSettings.addUser=Erabiltzaile berria
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
adminUserSettings.roles=Rolak
adminUserSettings.role=Rol
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Gorde Erabiltzailea
adminUserSettings.changeUserRole=Erabiltzailearen rola aldatu
adminUserSettings.authenticated=Authenticated
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Zure kontua blokeatu egin da.
login.signinTitle=Mesedez, hasi saioa
login.ssoSignIn=Hasi saioa Saioa hasteko modu bakarraren bidez
login.oauth2AutoCreateDisabled=OAUTH2 Sortu automatikoki erabiltzailea desgaituta dago
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Authorization request not found
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Inguruko area gutxienekoa:
ScannerImageSplit.selectText.8=Ezarri inguruko arearen gutxieneko balioa argazki batentzat
ScannerImageSplit.selectText.9=Ertzaren tamaina:
ScannerImageSplit.selectText.10=Ezarri gehitutako eta ezabatutako ertzaren tamaina irteeran ertz zuriak saihesteko (lehenetsia: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Kolorea
pdfToImage.grey=Gris-eskala
pdfToImage.blackwhite=Zuria eta Beltza (Datuak galdu ditzake!)
pdfToImage.submit=Bihurtu
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=Altuera (ur-marka bakoitzaren arteko espazioa bertikalean
watermark.selectText.7=Opakutasuna (0% - 100%):
watermark.selectText.8=Watermark Type:
watermark.selectText.9=Watermark Image:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Gehitu ur-marka
watermark.type.1=Text
watermark.type.2=Image
@@ -1143,10 +1124,3 @@ error.showStack=Show Stack Trace
error.copyStack=Copy Stack Trace
error.githubSubmit=GitHub - Submit a ticket
error.discordSubmit=Discord - Submit Support post
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=Utilisateur non trouvé.
incorrectPasswordMessage=Le mot de passe actuel est incorrect.
usernameExistsMessage=Le nouveau nom dutilisateur existe déjà.
invalidUsernameMessage=Nom dutilisateur invalide, le nom dutilisateur ne peut contenir que des lettres, des chiffres et les caractères spéciaux suivants @._+- ou doit être une adresse e-mail valide.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Impossible de supprimer lutilisateur actuellement connecté.
deleteUsernameExistsMessage=Le nom dutilisateur nexiste pas et ne peut pas être supprimé.
downgradeCurrentUserMessage=Impossible de rétrograder le rôle de l'utilisateur actuel.
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=Impossible de rétrograder le rôle de l'utilisateur actuel. Par conséquent, l'utilisateur actuel ne sera pas affiché.
userAlreadyExistsOAuthMessage=L'utilisateur existe déjà en tant qu'utilisateur OAuth2.
userAlreadyExistsWebMessage=L'utilisateur existe déjà en tant qu'utilisateur Web.
@@ -179,7 +177,6 @@ adminUserSettings.user=Utilisateur
adminUserSettings.addUser=Ajouter un utilisateur
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Le nom d'utilisateur ne peut contenir que des lettres, des chiffres et les caractères spéciaux suivants @._+- ou doit être une adresse e-mail valide.
adminUserSettings.roles=Rôles
adminUserSettings.role=Rôle
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Forcer lutilisateur à changer son nom dutil
adminUserSettings.submit=Ajouter
adminUserSettings.changeUserRole=Changer le rôle de l'utilisateur
adminUserSettings.authenticated=Authentifié
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=eBook vers PDF
home.BookToPDF.desc=Convertit les formats de livres/bandes dessinées en PDF à l'aide de calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Votre compte a été verrouillé.
login.signinTitle=Veuillez vous connecter
login.ssoSignIn=Se connecter via l'authentification unique
login.oauth2AutoCreateDisabled=OAUTH2 Création automatique d'utilisateur désactivée
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Demande d'autorisation introuvable
login.oauth2InvalidUserInfoResponse=Réponse contenant les informations de l'utilisateur est invalide
login.oauth2invalidRequest=Requête invalide
login.oauth2AccessDenied=Accès refusé
login.oauth2InvalidTokenResponse=Réponse contenant le jeton est invalide
login.oauth2InvalidIdToken=Jeton d'identification invalide
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Surface de contour minimale
ScannerImageSplit.selectText.8=Définit la surface de contour minimale pour une photo (par défaut : 500).
ScannerImageSplit.selectText.9=Taille de la bordure
ScannerImageSplit.selectText.10=Définit la taille de la bordure ajoutée et supprimée pour éviter les bordures blanches dans la sortie (par défaut : 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Couleur
pdfToImage.grey=Niveaux de gris
pdfToImage.blackwhite=Noir et blanc (peut engendrer une perte de données !)
pdfToImage.submit=Convertir
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=heightSpacer (espace entre chaque filigrane verticalement
watermark.selectText.7=Opacité (de 0% à 100%)
watermark.selectText.8=Type de filigrane
watermark.selectText.9=Image du filigrane
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Ajouter un filigrane
watermark.type.1=Text
watermark.type.2=Image
@@ -1143,10 +1124,3 @@ error.showStack=Afficher la Stack Trace
error.copyStack=Copier la Stack Trace
error.githubSubmit=GitHub - Créer un ticket
error.discordSubmit=Discord - Poster un message de demande dassistance
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=Úsáideoir gan aimsiú.
incorrectPasswordMessage=Tá an pasfhocal reatha mícheart.
usernameExistsMessage=Tá Ainm Úsáideora Nua ann cheana féin.
invalidUsernameMessage=Ainm úsáideora neamhbhailí, ní féidir ach litreacha, uimhreacha agus na carachtair speisialta seo a leanas @._+- a bheith san ainm úsáideora nó ní mór gur seoladh ríomhphoist bailí é.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=Ní mór Pasfhocal Nua agus Deimhnigh Pasfhocal Nua a bheith ag teacht leis.
deleteCurrentUserMessage=Ní féidir an t-úsáideoir atá logáilte isteach faoi láthair a scriosadh.
deleteUsernameExistsMessage=Níl an t-ainm úsáideora ann agus ní féidir é a scriosadh.
downgradeCurrentUserMessage=Ní féidir ról an úsáideora reatha a íosghrádú
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=Ní féidir ról an úsáideora reatha a íosghrádú. Mar sin, ní thaispeánfar an t-úsáideoir reatha.
userAlreadyExistsOAuthMessage=Tá an t-úsáideoir ann cheana mar úsáideoir OAuth2.
userAlreadyExistsWebMessage=Tá an t-úsáideoir ann cheana féin mar úsáideoir gréasáin.
@@ -179,7 +177,6 @@ adminUserSettings.user=Úsáideoir
adminUserSettings.addUser=Cuir Úsáideoir Nua leis
adminUserSettings.deleteUser=Scrios Úsáideoir
adminUserSettings.confirmDeleteUser=Ar cheart an t-úsáideoir a scriosadh?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Ní féidir ach litreacha, uimhreacha agus na carachtair speisialta seo a leanas @._+- a bheith san ainm úsáideora nó ní mór gur seoladh ríomhphoist bailí é.
adminUserSettings.roles=Róil
adminUserSettings.role=Ról
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Cuir iallach ar an úsáideoir pasfhocal a athrú
adminUserSettings.submit=Sábháil Úsáideoir
adminUserSettings.changeUserRole=Athraigh Ról an Úsáideora
adminUserSettings.authenticated=Fíordheimhnithe
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Iompórtáil / Easpórtáil Bunachar Sonraí
@@ -471,10 +461,6 @@ home.BookToPDF.title=Leabhar a thiontú go PDF
home.BookToPDF.desc=Tiontaíonn sé formáidí Leabhair/Comics go PDF ag baint úsáide as calibre
BookToPDF.tags=Leabhar, Comic, Calibre, Tiontaigh, manga, amazon, kindle, epub, mobi, azw3, docx, rtf, txt, html, lit, fb2, pdb, lrf
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Tá do chuntas glasáilte.
login.signinTitle=Sínigh isteach le do thoil
login.ssoSignIn=Logáil isteach trí Chlárú Aonair
login.oauth2AutoCreateDisabled=OAUTH2 Uath-Chruthaigh Úsáideoir faoi Mhíchumas
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Níor aimsíodh iarratas údaraithe
login.oauth2InvalidUserInfoResponse=Freagra Neamhbhailí Faisnéise Úsáideora
login.oauth2invalidRequest=Iarratas Neamhbhailí
login.oauth2AccessDenied=Rochtain Diúltaithe
login.oauth2InvalidTokenResponse=Freagra Comhartha Neamhbhailí
login.oauth2InvalidIdToken=Comhartha Aitheantais Neamhbhailí
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Íos-Limistéar Comhrianta:
ScannerImageSplit.selectText.8=Socraíonn sé an tairseach íosta achar comhrianta le haghaidh grianghraf
ScannerImageSplit.selectText.9=Méid na Teorann:
ScannerImageSplit.selectText.10=Socraíonn sé méid na teorann a chuirtear leis agus a bhaintear chun teorainneacha bán a chosc san aschur (réamhshocraithe: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Dath
pdfToImage.grey=Scála Liath
pdfToImage.blackwhite=Dubh agus Bán (Dfhéadfadh sonraí a chailleadh!)
pdfToImage.submit=Tiontaigh
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=spásaire airde (Spás idir gach comhartha uisce go hinge
watermark.selectText.7=Teimhneacht (0% - 100%):
watermark.selectText.8=Cineál Comhartha Uisce:
watermark.selectText.9=Íomhá Comhartha Uisce:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Cuir Uisce leis
watermark.type.1=Téacs
watermark.type.2=Íomha
@@ -1143,10 +1124,3 @@ error.showStack=Taispeáin Stack Trace
error.copyStack=Cóipeáil Stack Trace
error.githubSubmit=GitHub - Cuir ticéad isteach
error.discordSubmit=Discord - Cuir post Tacaíochta
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=उपयोगकर्ता नहीं मिला।
incorrectPasswordMessage=वर्तमान पासवर्ड गलत है।
usernameExistsMessage=नया उपयोगकर्ता नाम पहले से मौजूद है।
invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
downgradeCurrentUserMessage=मौजूदा यूज़र की भूमिका को डाउनग्रेड नहीं किया जा सकता
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=मौजूदा यूज़र की भूमिका को डाउनग्रेड नहीं किया जा सकता। इसलिए, वर्तमान उपयोगकर्ता को नहीं दिखाया जाएगा।
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
userAlreadyExistsWebMessage=The user already exists as an web user.
@@ -179,7 +177,6 @@ adminUserSettings.user=उपयोगकर्ता
adminUserSettings.addUser=नया उपयोगकर्ता जोड़ें
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
adminUserSettings.roles=रोल्स
adminUserSettings.role=रोल
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=उपयोगकर्ता को लॉगि
adminUserSettings.submit=उपयोगकर्ता को सहेजें
adminUserSettings.changeUserRole=यूज़र की भूमिका बदलें
adminUserSettings.authenticated=Authenticated
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=आपका खाता लॉक कर दिया गया
login.signinTitle=कृपया साइन इन करें
login.ssoSignIn=सिंगल साइन - ऑन के ज़रिए लॉग इन करें
login.oauth2AutoCreateDisabled=OAUTH2 ऑटो - क्रिएट यूज़र अक्षम किया गया
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Authorization request not found
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=न्यूनतम कंटोर क्ष
ScannerImageSplit.selectText.8=फोटो के लिए न्यूनतम कंटोर क्षेत्र थ्रेशोल्ड को सेट करता है।
ScannerImageSplit.selectText.9=बॉर्डर का आकार:
ScannerImageSplit.selectText.10=निकालने और जोड़ने के लिए जोड़ा जाने वाला बॉर्डर का आकार सेट करता है ताकि आउटपुट में सफेद बॉर्डर न आए (डिफ़ॉल्ट: 1)।
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=रंगीन
pdfToImage.grey=ग्रे स्केल
pdfToImage.blackwhite=काला और सफेद (डेटा खो सकता है!)
pdfToImage.submit=परिवर्तित करें
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=ऊंचाई स्पेसर (प्रत्ये
watermark.selectText.7=अपारदर्शिता (0% - 100%):
watermark.selectText.8=वॉटरमार्क प्रकार:
watermark.selectText.9=वॉटरमार्क छवि:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=वॉटरमार्क जोड़ें
watermark.type.1=Text
watermark.type.2=Image
@@ -1143,10 +1124,3 @@ error.showStack=Show Stack Trace
error.copyStack=Copy Stack Trace
error.githubSubmit=GitHub - Submit a ticket
error.discordSubmit=Discord - Submit Support post
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=Korisnik nije pronađen.
incorrectPasswordMessage=Kriva zaporka.
usernameExistsMessage=Korisničko ime već postoji
invalidUsernameMessage=Nevažeće korisničko ime, korisničko ime može sadržavati samo slova, brojke i sljedeće posebne znakove @._+- ili mora biti važeća adresa e-pošte.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Nije moguće izbrisati trenutno prijavljenog korisnika.
deleteUsernameExistsMessage=Korisničko ime ne postoji i ne može se izbrisati.
downgradeCurrentUserMessage=Nije moguće vratiti unazad ulogu trenutnog korisnika
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=Nije moguće vratiti unazad ulogu trenutnog korisnika. Dakle, trenutni korisnik neće biti prikazan.
userAlreadyExistsOAuthMessage=Korisnik već postoji kao OAuth2 korisnik.
userAlreadyExistsWebMessage=Korisnik već postoji kao web korisnik.
@@ -179,7 +177,6 @@ adminUserSettings.user=Korisnik
adminUserSettings.addUser=Dodaj novog korisnika
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Korisničko ime može sadržavati samo slova, brojke i sljedeće posebne znakove @._+- ili mora biti važeća adresa e-pošte.
adminUserSettings.roles=Uloge
adminUserSettings.role=Uloga
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Prisiliti korisnika da promijeni lozinku prilikom
adminUserSettings.submit=Spremi korisnika
adminUserSettings.changeUserRole=Promijenite korisničku ulogu
adminUserSettings.authenticated=Autentificirano
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Book u PDF
home.BookToPDF.desc=Pretvara format knjige/stripa u PDF format pomoću calibre
BookToPDF.tags=Knjiga,Strip,Calibre,Pretvori,manga,amazon,kindle
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Vaš račun je zaključan.
login.signinTitle=Molimo vas da se prijavite
login.ssoSignIn=Prijavite se putem jedinstvene prijave
login.oauth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Zahtjev za autorizaciju nije pronađen
login.oauth2InvalidUserInfoResponse=Nevažeće informacije o korisniku
login.oauth2invalidRequest=Neispravan zahtjev
login.oauth2AccessDenied=Pristup odbijen
login.oauth2InvalidTokenResponse=Nevažeći odgovor tokena
login.oauth2InvalidIdToken=Nevažeći ID token
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Minimalna konturna površina:
ScannerImageSplit.selectText.8=Postavlja minimalni prag površine konture za fotografiju
ScannerImageSplit.selectText.9=Veličina obruba:
ScannerImageSplit.selectText.10=Postavlja veličinu obruba koji se dodaje i uklanja kako bi se spriječili bijeli obrubi u ispisu (zadano: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Boja
pdfToImage.grey=Sivi tonovi
pdfToImage.blackwhite=Crno-bijelo (mogu se izgubiti podaci!)
pdfToImage.submit=Pretvori
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=Visina razmaka (Razmak između svakog vodenog žiga okomi
watermark.selectText.7=Neprozirnost (0% - 100%):
watermark.selectText.8=Vrsta vodenog žiga:
watermark.selectText.9=Slika vodenog žiga:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Dodaj vodeni žig
watermark.type.1=Tekst
watermark.type.2=Slika
@@ -1143,10 +1124,3 @@ error.showStack=Prikaži Stack Trace
error.copyStack=Kopiraj Stack Trace
error.githubSubmit=GitHub - Pošaljite ticket
error.discordSubmit=Discord - Pošalji objavu podrške
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=A felhasználó nem található.
incorrectPasswordMessage=A jelenlegi jelszó helytelen.
usernameExistsMessage=Az új felhasználónév már létezik.
invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
downgradeCurrentUserMessage=A jelenlegi felhasználó szerepkörét nem lehet visszaminősíteni
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=Az aktuális felhasználó szerepkörét nem lehet visszaminősíteni. Ezért az aktuális felhasználó nem jelenik meg.
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
userAlreadyExistsWebMessage=The user already exists as an web user.
@@ -179,7 +177,6 @@ adminUserSettings.user=Felhasználó
adminUserSettings.addUser=Új felhasználó hozzáadása
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
adminUserSettings.roles=Szerepek
adminUserSettings.role=Szerep
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Kényszerítse a felhasználót a felhasználóné
adminUserSettings.submit=Felhasználó mentése
adminUserSettings.changeUserRole=Felhasználó szerepkörének módosítása
adminUserSettings.authenticated=Authenticated
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=A fiókja zárolva lett!
login.signinTitle=Kérjük, jelentkezzen be!
login.ssoSignIn=Bejelentkezés egyszeri bejelentkezéssel
login.oauth2AutoCreateDisabled=OAUTH2 Felhasználó automatikus létrehozása letiltva
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Authorization request not found
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Minimális kontúr terület:
ScannerImageSplit.selectText.8=A fotók minimális kontúrterületének beállítása
ScannerImageSplit.selectText.9=Keret mérete:
ScannerImageSplit.selectText.10=A hozzáadott és eltávolított keret méretének beállítása a fehér keretek elkerülése érdekében a kimeneten (alapértelmezett: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=színes
pdfToImage.grey=szürkeárnyalatos
pdfToImage.blackwhite=fekete-fehér (adatvesztéssel járhat!)
pdfToImage.submit=Átalakítás
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=heightSpacer (Hely a vízjelek között függőlegesen):
watermark.selectText.7=Átlátszóság (0% - 100%):
watermark.selectText.8=Vízjel típusa:
watermark.selectText.9=Vízjel képe:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Vízjel hozzáadása
watermark.type.1=Text
watermark.type.2=Image
@@ -1143,10 +1124,3 @@ error.showStack=Show Stack Trace
error.copyStack=Copy Stack Trace
error.githubSubmit=GitHub - Submit a ticket
error.discordSubmit=Discord - Submit Support post
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

View File

@@ -55,12 +55,10 @@ userNotFoundMessage=Pengguna tidak ditemukan.
incorrectPasswordMessage=Kata sandi saat ini salah.
usernameExistsMessage=Nama pengguna baru sudah ada.
invalidUsernameMessage=Invalid username, username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
downgradeCurrentUserMessage=Tidak dapat menurunkan peran pengguna saat ini
disabledCurrentUserMessage=The current user cannot be disabled
downgradeCurrentUserLongMessage=Tidak dapat menurunkan peran pengguna saat ini. Oleh karena itu, pengguna saat ini tidak akan ditampilkan.
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
userAlreadyExistsWebMessage=The user already exists as an web user.
@@ -179,7 +177,6 @@ adminUserSettings.user=Pengguna
adminUserSettings.addUser=Tambahkan Pengguna Baru
adminUserSettings.deleteUser=Delete User
adminUserSettings.confirmDeleteUser=Should the user be deleted?
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
adminUserSettings.usernameInfo=Username can only contain letters, numbers and the following special characters @._+- or must be a valid email address.
adminUserSettings.roles=Peran
adminUserSettings.role=Peran
@@ -193,13 +190,6 @@ adminUserSettings.forceChange=Memaksa pengguna untuk mengubah nama pengguna/kata
adminUserSettings.submit=Simpan Pengguna
adminUserSettings.changeUserRole=Ubah Peran Pengguna
adminUserSettings.authenticated=Authenticated
adminUserSettings.editOwnProfil=Edit own profile
adminUserSettings.enabledUser=enabled user
adminUserSettings.disabledUser=disabled user
adminUserSettings.activeUsers=Active Users:
adminUserSettings.disabledUsers=Disabled Users:
adminUserSettings.totalUsers=Total Users:
adminUserSettings.lastRequest=Last Request
database.title=Database Import/Export
@@ -471,10 +461,6 @@ home.BookToPDF.title=Book to PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.removeImagePdf.title=Remove image
home.removeImagePdf.desc=Remove image from PDF to reduce file size
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
###########################
# #
@@ -491,14 +477,12 @@ login.locked=Akun Anda telah dikunci.
login.signinTitle=Silakan masuk
login.ssoSignIn=Masuk melalui Single Sign - on
login.oauth2AutoCreateDisabled=OAUTH2 Buat Otomatis Pengguna Dinonaktifkan
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
login.oauth2RequestNotFound=Authorization request not found
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
login.oauth2invalidRequest=Invalid Request
login.oauth2AccessDenied=Access Denied
login.oauth2InvalidTokenResponse=Invalid Token Response
login.oauth2InvalidIdToken=Invalid Id Token
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
#auto-redact
@@ -775,7 +759,6 @@ ScannerImageSplit.selectText.7=Area Kontur Minimum:
ScannerImageSplit.selectText.8=Menetapkan ambang batas area kontur minimum untuk foto
ScannerImageSplit.selectText.9=Ukuran Batas:
ScannerImageSplit.selectText.10=Menetapkan ukuran batas yang ditambahkan dan dihapus untuk mencegah batas putih pada output (default: 1).
ScannerImageSplit.info=Python is not installed. It is required to run.
#OCR
@@ -926,7 +909,6 @@ pdfToImage.color=Warna
pdfToImage.grey=Skala abu-abu
pdfToImage.blackwhite=Black and White (Bisa kehilangan data!)
pdfToImage.submit=Konversi
pdfToImage.info=Python is not installed. Required for WebP conversion.
#addPassword
@@ -963,7 +945,6 @@ watermark.selectText.6=heightSpacer (Spasi diantara setiap watermark vertikal):
watermark.selectText.7=Opacity (0% - 100%):
watermark.selectText.8=Tipe Watermark:
watermark.selectText.9=Gambar Watermark:
watermark.selectText.10=Convert PDF to PDF-Image
watermark.submit=Tambahkan Watermark
watermark.type.1=Text
watermark.type.2=Image
@@ -1143,10 +1124,3 @@ error.showStack=Show Stack Trace
error.copyStack=Copy Stack Trace
error.githubSubmit=GitHub - Submit a ticket
error.discordSubmit=Discord - Submit Support post
#remove-image
removeImage.title=Remove image
removeImage.header=Remove image
removeImage.removeImage=Remove image
removeImage.submit=Remove image

Some files were not shown because too many files have changed in this diff Show More