diff --git a/CONTRIBUTORS b/CONTRIBUTORS
new file mode 100644
index 0000000..9355790
--- /dev/null
+++ b/CONTRIBUTORS
@@ -0,0 +1,322 @@
+CONTRIBUTORS
+
+This file is automatically generated. DO NOT EDIT MANUALLY.
+Generated on: 2025-08-09T14:14:17.221105Z
+
+Upstream project: https://github.com/janeczku/calibre-web
+Fork project (Calibre-Web Automated, since 2024): https://github.com/crocodilestick/calibre-web-automated
+
+License notice: This fork retains attribution to original Calibre-Web contributors in accordance with GPL-3.0-or-later.
+
+Copyright (C) 2018-2025 Calibre-Web contributors
+Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# Upstream Contributors (janeczku/calibre-web)
+
+- OzzieIsaacs (anon) (2717 commits)
+- Ozzie Isaacs (anon) (257 commits)
+- cbartondock (96 commits)
+- idalin (69 commits)
+- cervinko (68 commits)
+- irfan-ansari-au28 (64 commits)
+- jkrehm (62 commits)
+- Jack Darlington (anon) (51 commits)
+- hexeth (anon) (44 commits)
+- shavitmichael (44 commits)
+- Jan Broer (anon) (41 commits)
+- blitzmann (38 commits)
+- mmonkey (33 commits)
+- jvoisin (30 commits)
+- bodybybuddha (26 commits)
+- ElQuimm (26 commits)
+- janeczku (anon) (26 commits)
+- xlivevil (24 commits)
+- ghighi3f (23 commits)
+- Virgil Grigoras (anon) (23 commits)
+- Krakinou (21 commits)
+- pthiben (anon) (19 commits)
+- radoslawkierznowski (19 commits)
+- kyos (anon) (18 commits)
+- Thore Schillmann (anon) (18 commits)
+- quarz12 (16 commits)
+- ok11 (13 commits)
+- pwr (13 commits)
+- mapi68 (12 commits)
+- Kyosfonica (11 commits)
+- otapi (11 commits)
+- andy29485 (10 commits)
+- Jony (anon) (10 commits)
+- leram84 (10 commits)
+- nykul (10 commits)
+- oskardotglobal (10 commits)
+- subdiox (10 commits)
+- XZVB12 (10 commits)
+- celogeek (9 commits)
+- GarckaMan (9 commits)
+- Knepherbird (9 commits)
+- Yamakuni (anon) (9 commits)
+- 89jd (8 commits)
+- aroberts (8 commits)
+- collerek (7 commits)
+- Jan B (anon) (7 commits)
+- lemmsh (7 commits)
+- wuqi (7 commits)
+- alfred82santa (6 commits)
+- Kyos (anon) (6 commits)
+- limeade23 (6 commits)
+- Radosław Kierznowski (anon) (6 commits)
+- akhy (5 commits)
+- AsherMaximum (5 commits)
+- Daniel (anon) (5 commits)
+- gjutras (5 commits)
+- horus68 (5 commits)
+- JanB (anon) (5 commits)
+- Ainsley Pereira (anon) (4 commits)
+- csitko (4 commits)
+- Faisal Alghamdi (anon) (4 commits)
+- flying-sausages (4 commits)
+- garf1242 (4 commits)
+- Illia-M (4 commits)
+- Jan Guzej (anon) (4 commits)
+- joshobrienau (4 commits)
+- Oreolek (4 commits)
+- Petipopotam (4 commits)
+- stfrbrntyu (4 commits)
+- trasba (4 commits)
+- webysther (4 commits)
+- wildthyme (4 commits)
+- acciobugs (3 commits)
+- aliceout (3 commits)
+- byword77 (3 commits)
+- chintogtokh (3 commits)
+- dickreckard (anon) (3 commits)
+- eddriesen (3 commits)
+- ground7 (3 commits)
+- iz7iz7iz (3 commits)
+- jango (3 commits)
+- JFernando122 (3 commits)
+- Kennyl (3 commits)
+- lorek123 (anon) (3 commits)
+- OlivierMaire (3 commits)
+- SiphonSquirrel (3 commits)
+- Thovi98 (3 commits)
+- verglor (3 commits)
+- yunimoo (anon) (3 commits)
+- afrimberger (2 commits)
+- Andrej Kralj (anon) (2 commits)
+- andrerfcsantos (2 commits)
+- Angel Docampo (anon) (2 commits)
+- AnonTester (2 commits)
+- beedaddy (2 commits)
+- bharatknv (2 commits)
+- CHBMB (2 commits)
+- Clément Poissonnier (anon) (2 commits)
+- Cyberax (2 commits)
+- daniel-edwards-iress (2 commits)
+- drizuid (2 commits)
+- fernando-mesquita (2 commits)
+- flashlab (2 commits)
+- grunjol (2 commits)
+- h1f0x (2 commits)
+- halkeye (2 commits)
+- hitsounds (2 commits)
+- issmirnov (2 commits)
+- jef (2 commits)
+- jfenske89 (2 commits)
+- jim3ma (2 commits)
+- juanfernandovillahernandez (2 commits)
+- KN4CK3R (2 commits)
+- marblepebble (2 commits)
+- nanu-c (2 commits)
+- norangebit (2 commits)
+- Northguy (2 commits)
+- peperunas (2 commits)
+- petersonev (2 commits)
+- rafarq (2 commits)
+- rscmbbng (2 commits)
+- rtrox (2 commits)
+- shivishbrahma (2 commits)
+- tader (2 commits)
+- teflontoni (2 commits)
+- tintinmar1995 (2 commits)
+- tobiasbayer (2 commits)
+- UFervor (2 commits)
+- UsamaFoad (2 commits)
+- victorhck (2 commits)
+- vigri (2 commits)
+- Wladefant (2 commits)
+- Zaroz (2 commits)
+- acsulli (1 commits)
+- Admiral Laser Beard (anon) (1 commits)
+- adocampo (1 commits)
+- aexvir (1 commits)
+- akushsky (1 commits)
+- Allram (1 commits)
+- altair (1 commits)
+- anatoliifetisov (1 commits)
+- andylizi (1 commits)
+- antar37 (1 commits)
+- Anton Konyshev (anon) (1 commits)
+- apetresc (1 commits)
+- aptalca (1 commits)
+- archont94 (1 commits)
+- aribes (1 commits)
+- ariesdevil (1 commits)
+- arihid (1 commits)
+- auveele (1 commits)
+- BeckyDTP (1 commits)
+- bernizt (1 commits)
+- boosh (1 commits)
+- Braincoke (anon) (1 commits)
+- brandoningli (1 commits)
+- captainwong (1 commits)
+- chadberg (1 commits)
+- coelebs (1 commits)
+- ConstrictM (1 commits)
+- contributor (1 commits)
+- cryptkiddy (1 commits)
+- danielamazza (1 commits)
+- databoy2k (1 commits)
+- Dave Mogle (anon) (1 commits)
+- decentral1se (1 commits)
+- dependabot[bot] (1 commits)
+- dfn-certling (1 commits)
+- dgliwka (1 commits)
+- dmd (1 commits)
+- dmistomin (1 commits)
+- dnahurnyi (1 commits)
+- dotlambda (1 commits)
+- ducbachvan (1 commits)
+- Dunrar (1 commits)
+- Efreak (1 commits)
+- elelay (1 commits)
+- elfcan (1 commits)
+- epsilon-0 (1 commits)
+- evijayan2 (1 commits)
+- fabwu (1 commits)
+- Feige-cn (1 commits)
+- Fmstrat (1 commits)
+- geekifier (1 commits)
+- gonzalo (1 commits)
+- gorgobacka (1 commits)
+- GrmpPnda (1 commits)
+- growfrow (1 commits)
+- GruberMarkus (1 commits)
+- gwenhael-le-moine (1 commits)
+- h3xten (1 commits)
+- halink0803 (1 commits)
+- ImanSharaf (1 commits)
+- Irishsmurf (1 commits)
+- JamesMcNee (1 commits)
+- jathak (1 commits)
+- jayrhynas (1 commits)
+- Jeroen Kroese (anon) (1 commits)
+- jgillman (1 commits)
+- jhellingman (1 commits)
+- jianyun.zhao (anon) (1 commits)
+- JOHAE96 (1 commits)
+- johnnyasantoss (1 commits)
+- JonathanHerrewijnen (1 commits)
+- jorti (1 commits)
+- joshumax (1 commits)
+- Julian Naydichev (anon) (1 commits)
+- jureqq (1 commits)
+- jvonau (1 commits)
+- JVT038 (1 commits)
+- kianmeng (1 commits)
+- knobunc (1 commits)
+- Kreeblah (1 commits)
+- L0garithmic (1 commits)
+- LawssssCat (1 commits)
+- leexia (1 commits)
+- leyan (1 commits)
+- Lorek (anon) (1 commits)
+- Louis Jencka (anon) (1 commits)
+- lucaparsani (anon) (1 commits)
+- lucindaq (1 commits)
+- lunananananananana (1 commits)
+- malletfils (1 commits)
+- manad777 (1 commits)
+- Martreides (1 commits)
+- matthazinski (1 commits)
+- MeanderingCode (1 commits)
+- minakmostoles (1 commits)
+- mingyue-aai (1 commits)
+- mingyue-gao (1 commits)
+- MisLink (1 commits)
+- moben (1 commits)
+- nelsg2 (1 commits)
+- nferrari (1 commits)
+- NickWick13 (1 commits)
+- oguzkilcan (1 commits)
+- overhacked (1 commits)
+- PhracturedBlue (1 commits)
+- pjeby (1 commits)
+- pkorovin (1 commits)
+- Polarolouis (1 commits)
+- pollitor (1 commits)
+- potatoeggy (1 commits)
+- qx100 (1 commits)
+- Rewerson (1 commits)
+- Robbinsch (1 commits)
+- robochud (1 commits)
+- root (anon) (1 commits)
+- ruben-herold (1 commits)
+- ryanlong1004 (1 commits)
+- sartoshi-foot-dao (1 commits)
+- skiman6010 (1 commits)
+- SpaceWhite (1 commits)
+- theopsall (1 commits)
+- Thijxx (1 commits)
+- tomjmul (1 commits)
+- vagra (1 commits)
+- viljasenville (1 commits)
+- Vistaus (1 commits)
+- vrabe (1 commits)
+- W1ndst0rm (1 commits)
+- wadefelix (1 commits)
+- wanoo (1 commits)
+- whacloud (1 commits)
+- whilenot-dev (1 commits)
+- Wolfenk (1 commits)
+- wolviex (1 commits)
+- Wouter Dijk (anon) (1 commits)
+- WouterKoch (1 commits)
+- wuwei-12138 (1 commits)
+- xybydy (1 commits)
+- yjouanique (anon) (1 commits)
+- ytilis (1 commits)
+- zelazna (1 commits)
+- zhiyue (1 commits)
+# Fork Contributors (crocodilestick/calibre-web-automated)
+
+- crocodilestick (436 commits)
+- jmarmstrong1207 (73 commits)
+- demitrix (30 commits)
+- sirwolfgang (22 commits)
+- alva-seal (6 commits)
+- angelicadvocate (6 commits)
+- smevawala (5 commits)
+- Aymendje (3 commits)
+- Calychas (3 commits)
+- zikasak (3 commits)
+- chad3814 (2 commits)
+- coissac (2 commits)
+- FennyFatal (2 commits)
+- bcrdncola (1 commits)
+- Buco7854 (1 commits)
+- cascandaliato (1 commits)
+- Daniel Egberts (anon) (1 commits)
+- Dee76 (1 commits)
+- google-labs-jules[bot] (1 commits)
+- gx1400 (1 commits)
+- have-a-boy (1 commits)
+- Hobogrammer (anon) (1 commits)
+- jack1lee1995 (anon) (1 commits)
+- jspiers (1 commits)
+- morpheus65535 (1 commits)
+- n00b42 (1 commits)
+- NotaInutilis (1 commits)
+- r0b2g1t (1 commits)
+- robinwo (1 commits)
+- Rustymage (1 commits)
+- Turmaxx (1 commits)
diff --git a/check_spdx_headers.py b/check_spdx_headers.py
new file mode 100644
index 0000000..6e198e9
--- /dev/null
+++ b/check_spdx_headers.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
+"""
+check_spdx_headers.py
+
+Lightweight checker to ensure each staged source file (or specified paths) contains
+an SPDX identifier (GPL-3.0-or-later) and the Calibre-Web Automated attribution header.
+Intended for use in a pre-commit hook.
+
+Usage:
+ python check_spdx_headers.py [files...]
+If no files provided, it inspects staged git changes (added/copied/modified).
+
+Exit codes:
+ 0 = all good
+ 1 = problems found / errors
+
+Customize REQUIRED_SUBSTRINGS if needed.
+"""
+from __future__ import annotations
+
+import subprocess
+import sys
+import pathlib
+from typing import List
+
+REQUIRED_SUBSTRINGS = [
+ "Calibre-Web Automated", # attribution line
+ "SPDX-License-Identifier: GPL-3.0-or-later",
+]
+
+VALID_EXT = {".py", ".sh", ".js", ".ts", ".css", ".html"}
+
+EXCLUDE_DIR = {"__pycache__", ".git", "dist", "build", "node_modules", "venv", ".venv"}
+
+
+def staged_files() -> List[pathlib.Path]:
+ try:
+ out = subprocess.check_output([
+ "git", "diff", "--cached", "--name-only", "--diff-filter=ACM"
+ ], text=True)
+ except Exception:
+ return []
+ files = []
+ for line in out.splitlines():
+ p = pathlib.Path(line.strip())
+ if not p.exists():
+ continue
+ if any(part in EXCLUDE_DIR for part in p.parts):
+ continue
+ if p.suffix.lower() in VALID_EXT:
+ files.append(p)
+ return files
+
+
+def check_file(path: pathlib.Path) -> List[str]:
+ try:
+ text = path.read_text(encoding="utf-8", errors="ignore")
+ except Exception as e:
+ return [f"{path}: unable to read ({e})"]
+ missing = [s for s in REQUIRED_SUBSTRINGS if s not in text]
+ if missing:
+ return [f"{path}: missing -> {', '.join(missing)}"]
+ return []
+
+
+def main(argv: List[str]) -> int:
+ if len(argv) > 1:
+ targets = [pathlib.Path(a) for a in argv[1:]]
+ else:
+ targets = staged_files()
+
+ if not targets:
+ print("No files to check.")
+ return 0
+
+ problems: List[str] = []
+ for f in targets:
+ problems.extend(check_file(f))
+
+ if problems:
+ print("SPDX / attribution header check failed:")
+ for p in problems:
+ print(" ", p)
+ print("Hint: run: python update_spdx_headers.py --apply")
+ return 1
+ print("All headers OK.")
+ return 0
+
+
+if __name__ == "__main__": # pragma: no cover
+ raise SystemExit(main(sys.argv))
diff --git a/cps.py b/cps.py
index 039c2a5..192b562 100644
--- a/cps.py
+++ b/cps.py
@@ -1,21 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2022 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
import sys
diff --git a/cps/MyLoginManager.py b/cps/MyLoginManager.py
index 4490704..3219f77 100755
--- a/cps/MyLoginManager.py
+++ b/cps/MyLoginManager.py
@@ -1,24 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
-# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
-# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
-# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
-# apetresc, nanu-c, mutschler, GammaC0de, vuolter
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from .cw_login import LoginManager
from flask import session
diff --git a/cps/__init__.py b/cps/__init__.py
index 05ab668..b7f3dd3 100755
--- a/cps/__init__.py
+++ b/cps/__init__.py
@@ -1,24 +1,10 @@
# -*- coding: utf-8 -*-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
-# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
-# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
-# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
-# apetresc, nanu-c, mutschler
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
__package__ = "cps"
import sys
diff --git a/cps/about.py b/cps/about.py
index 548e0df..02a858f 100755
--- a/cps/about.py
+++ b/cps/about.py
@@ -1,24 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
-# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
-# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
-# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
-# apetresc, nanu-c, mutschler
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import sys
import platform
diff --git a/cps/admin.py b/cps/admin.py
index ac31caa..40d3284 100644
--- a/cps/admin.py
+++ b/cps/admin.py
@@ -1,24 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
-# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
-# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
-# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
-# apetresc, nanu-c, mutschler, GammaC0de, vuolter
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
import re
diff --git a/cps/audio.py b/cps/audio.py
index b17bc7b..cfaf1f0 100755
--- a/cps/audio.py
+++ b/cps/audio.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2024 Ozzieisaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
diff --git a/cps/cache_buster.py b/cps/cache_buster.py
index eb6b743..e60e83d 100755
--- a/cps/cache_buster.py
+++ b/cps/cache_buster.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2016-2019 jkrehm andy29485 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
# Inspired by https://github.com/ChrisTM/Flask-CacheBust
# Uses query strings so CSS font files are found without having to resort to absolute URLs
diff --git a/cps/clean_html.py b/cps/clean_html.py
index c687f15..1fd3ba2 100755
--- a/cps/clean_html.py
+++ b/cps/clean_html.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from . import logger
from lxml.etree import ParserError
diff --git a/cps/cli.py b/cps/cli.py
index e832b96..50b8e34 100755
--- a/cps/cli.py
+++ b/cps/cli.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import sys
import os
diff --git a/cps/comic.py b/cps/comic.py
index a4a4f13..e6d65e2 100755
--- a/cps/comic.py
+++ b/cps/comic.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2022 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
diff --git a/cps/config_sql.py b/cps/config_sql.py
index 17e30e1..39d6d4f 100644
--- a/cps/config_sql.py
+++ b/cps/config_sql.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2019 OzzieIsaacs, pwr
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
import sys
diff --git a/cps/constants.py b/cps/constants.py
index 187a736..2f1c932 100644
--- a/cps/constants.py
+++ b/cps/constants.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2019 OzzieIsaacs, pwr
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import sys
import os
diff --git a/cps/converter.py b/cps/converter.py
index 981b390..2c90f60 100755
--- a/cps/converter.py
+++ b/cps/converter.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2016-2019 Ben Bennett, OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
import re
diff --git a/cps/cover.py b/cps/cover.py
index 8111083..a28e485 100755
--- a/cps/cover.py
+++ b/cps/cover.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2022 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
diff --git a/cps/cw_advocate/__init__.py b/cps/cw_advocate/__init__.py
index 58407b7..f67dccd 100755
--- a/cps/cw_advocate/__init__.py
+++ b/cps/cw_advocate/__init__.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
#
# Copyright 2015 Jordan Milne
#
diff --git a/cps/cw_advocate/adapters.py b/cps/cw_advocate/adapters.py
index b15a141..dbbf483 100755
--- a/cps/cw_advocate/adapters.py
+++ b/cps/cw_advocate/adapters.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
#
# Copyright 2015 Jordan Milne
#
diff --git a/cps/cw_advocate/addrvalidator.py b/cps/cw_advocate/addrvalidator.py
index 0f14ce8..53f1c24 100755
--- a/cps/cw_advocate/addrvalidator.py
+++ b/cps/cw_advocate/addrvalidator.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
#
# Copyright 2015 Jordan Milne
#
diff --git a/cps/cw_advocate/api.py b/cps/cw_advocate/api.py
index 5a5ac20..6b3dade 100755
--- a/cps/cw_advocate/api.py
+++ b/cps/cw_advocate/api.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
#
# Copyright 2015 Jordan Milne
#
diff --git a/cps/cw_advocate/connection.py b/cps/cw_advocate/connection.py
index ce790ad..69ec919 100755
--- a/cps/cw_advocate/connection.py
+++ b/cps/cw_advocate/connection.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
#
# Copyright 2015 Jordan Milne
#
diff --git a/cps/cw_advocate/connectionpool.py b/cps/cw_advocate/connectionpool.py
index 3bbbfac..8027b75 100755
--- a/cps/cw_advocate/connectionpool.py
+++ b/cps/cw_advocate/connectionpool.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
#
# Copyright 2015 Jordan Milne
#
diff --git a/cps/cw_advocate/exceptions.py b/cps/cw_advocate/exceptions.py
index 5ff9852..7581e36 100755
--- a/cps/cw_advocate/exceptions.py
+++ b/cps/cw_advocate/exceptions.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
#
# Copyright 2015 Jordan Milne
#
diff --git a/cps/cw_advocate/poolmanager.py b/cps/cw_advocate/poolmanager.py
index d912d65..613e778 100755
--- a/cps/cw_advocate/poolmanager.py
+++ b/cps/cw_advocate/poolmanager.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
#
# Copyright 2015 Jordan Milne
#
diff --git a/cps/cw_babel.py b/cps/cw_babel.py
index cf043d5..2e7eb7d 100755
--- a/cps/cw_babel.py
+++ b/cps/cw_babel.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
from babel import negotiate_locale
from flask_babel import Babel, Locale
from babel.core import UnknownLocaleError
diff --git a/cps/cw_login/__init__.py b/cps/cw_login/__init__.py
index f2822ba..834dd1f 100755
--- a/cps/cw_login/__init__.py
+++ b/cps/cw_login/__init__.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
# from .__about__ import __version__
from .config import AUTH_HEADER_NAME
from .config import COOKIE_DURATION
diff --git a/cps/cw_login/config.py b/cps/cw_login/config.py
index fe2db2c..e99a408 100755
--- a/cps/cw_login/config.py
+++ b/cps/cw_login/config.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
from datetime import timedelta
#: The default name of the "remember me" cookie (``remember_token``)
diff --git a/cps/cw_login/login_manager.py b/cps/cw_login/login_manager.py
index a3714af..47e3852 100755
--- a/cps/cw_login/login_manager.py
+++ b/cps/cw_login/login_manager.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
from datetime import datetime
from datetime import timezone
from datetime import timedelta
diff --git a/cps/cw_login/mixins.py b/cps/cw_login/mixins.py
index 0b3a71b..9740fa5 100755
--- a/cps/cw_login/mixins.py
+++ b/cps/cw_login/mixins.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
class UserMixin:
"""
This provides default implementations for the methods that Flask-Login
diff --git a/cps/cw_login/signals.py b/cps/cw_login/signals.py
index cf9157f..7ac63da 100755
--- a/cps/cw_login/signals.py
+++ b/cps/cw_login/signals.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
from flask.signals import Namespace
_signals = Namespace()
diff --git a/cps/cw_login/utils.py b/cps/cw_login/utils.py
index b455e15..802e764 100755
--- a/cps/cw_login/utils.py
+++ b/cps/cw_login/utils.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
import hmac
import os
from functools import wraps
diff --git a/cps/cwa_functions.py b/cps/cwa_functions.py
index 61765b6..583ee01 100644
--- a/cps/cwa_functions.py
+++ b/cps/cwa_functions.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
from flask import Blueprint, redirect, flash, url_for, request, send_from_directory, abort, jsonify, current_app
from flask_babel import gettext as _
diff --git a/cps/db.py b/cps/db.py
index 1164163..1b0ec41 100644
--- a/cps/db.py
+++ b/cps/db.py
@@ -1,21 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2012-2019 mutschler, cervinko, ok11, jkrehm, nanu-c, Wineliva,
-# pjeby, elelay, idalin, Ozzieisaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
import re
diff --git a/cps/debug_info.py b/cps/debug_info.py
index 473b46d..cd9051b 100755
--- a/cps/debug_info.py
+++ b/cps/debug_info.py
@@ -1,21 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2012-2019 cervinko, idalin, SiphonSquirrel, ouzklcn, akushsky,
-# OzzieIsaacs, bodybybuddha, jkrehm, matthazinski, janeczku
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import shutil
import glob
diff --git a/cps/dep_check.py b/cps/dep_check.py
index 0575aa5..99226ba 100755
--- a/cps/dep_check.py
+++ b/cps/dep_check.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
import os
import re
import sys
diff --git a/cps/editbooks.py b/cps/editbooks.py
index 6efa1cd..1ad749a 100644
--- a/cps/editbooks.py
+++ b/cps/editbooks.py
@@ -1,24 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
-# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
-# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
-# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
-# apetresc, nanu-c, mutschler
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
from datetime import datetime, timezone
diff --git a/cps/embed_helper.py b/cps/embed_helper.py
index 2a4ce34..688fe3d 100755
--- a/cps/embed_helper.py
+++ b/cps/embed_helper.py
@@ -1,20 +1,10 @@
# -*- coding: utf-8 -*-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2024 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
from uuid import uuid4
import os
diff --git a/cps/epub.py b/cps/epub.py
index 626e68e..54bfbed 100755
--- a/cps/epub.py
+++ b/cps/epub.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018 lemmsh, Kennyl, Kyosfonica, matthazinski
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
import zipfile
diff --git a/cps/epub_helper.py b/cps/epub_helper.py
index 4517f2c..038e602 100755
--- a/cps/epub_helper.py
+++ b/cps/epub_helper.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018 lemmsh, Kennyl, Kyosfonica, matthazinski
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import zipfile
from lxml import etree
diff --git a/cps/error_handler.py b/cps/error_handler.py
index 92d3e87..04c5768 100755
--- a/cps/error_handler.py
+++ b/cps/error_handler.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2020 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import traceback
diff --git a/cps/fb2.py b/cps/fb2.py
index 81eae57..727432f 100755
--- a/cps/fb2.py
+++ b/cps/fb2.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018 lemmsh, cervinko, OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from lxml import etree
diff --git a/cps/file_helper.py b/cps/file_helper.py
index a9708f1..7be745a 100755
--- a/cps/file_helper.py
+++ b/cps/file_helper.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2023 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from tempfile import gettempdir
import os
diff --git a/cps/fs.py b/cps/fs.py
index 996499c..ee9260b 100755
--- a/cps/fs.py
+++ b/cps/fs.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2020 mmonkey
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from . import logger
from .constants import CACHE_DIR
diff --git a/cps/gdrive.py b/cps/gdrive.py
index 07795b5..dfabce8 100755
--- a/cps/gdrive.py
+++ b/cps/gdrive.py
@@ -1,24 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
-# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
-# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
-# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
-# apetresc, nanu-c, mutschler
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
import hashlib
diff --git a/cps/gdriveutils.py b/cps/gdriveutils.py
index 7bff089..5fcdb7d 100755
--- a/cps/gdriveutils.py
+++ b/cps/gdriveutils.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018 idalin, OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
import json
diff --git a/cps/gevent_wsgi.py b/cps/gevent_wsgi.py
index ab98840..69e04b2 100755
--- a/cps/gevent_wsgi.py
+++ b/cps/gevent_wsgi.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2022 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from datetime import datetime
from gevent.pywsgi import WSGIHandler
diff --git a/cps/helper.py b/cps/helper.py
index 775c153..d30fdb9 100755
--- a/cps/helper.py
+++ b/cps/helper.py
@@ -1,21 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2012-2019 cervinko, idalin, SiphonSquirrel, ouzklcn, akushsky,
-# OzzieIsaacs, bodybybuddha, jkrehm, matthazinski, janeczku
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
import random
diff --git a/cps/isoLanguages.py b/cps/isoLanguages.py
index a34cd34..f461d91 100755
--- a/cps/isoLanguages.py
+++ b/cps/isoLanguages.py
@@ -1,20 +1,10 @@
# -*- coding: utf-8 -*-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2019 pwr
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
import sys
from .iso_language_names import LANGUAGE_NAMES as _LANGUAGE_NAMES
diff --git a/cps/iso_language_names.py b/cps/iso_language_names.py
index 9248443..7f0a7e7 100755
--- a/cps/iso_language_names.py
+++ b/cps/iso_language_names.py
@@ -1,4 +1,9 @@
# -*- coding: utf-8 -*-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2019 OzzieIsaacs, pwr
diff --git a/cps/jinjia.py b/cps/jinjia.py
index 3db3e88..dc46795 100755
--- a/cps/jinjia.py
+++ b/cps/jinjia.py
@@ -1,24 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
-# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
-# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
-# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
-# apetresc, nanu-c, mutschler
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
# custom jinja filters
diff --git a/cps/kobo.py b/cps/kobo.py
index e2e63b7..b9a8d15 100644
--- a/cps/kobo.py
+++ b/cps/kobo.py
@@ -1,21 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 shavitmichael, OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import base64
from datetime import datetime, timezone
diff --git a/cps/kobo_auth.py b/cps/kobo_auth.py
index 416307d..0382941 100755
--- a/cps/kobo_auth.py
+++ b/cps/kobo_auth.py
@@ -1,22 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 shavitmichael, OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
"""This module is used to control authentication/authorization of Kobo sync requests.
This module also includes research notes into the auth protocol used by Kobo devices.
diff --git a/cps/kobo_sync_status.py b/cps/kobo_sync_status.py
index 357b84e..6063df4 100755
--- a/cps/kobo_sync_status.py
+++ b/cps/kobo_sync_status.py
@@ -1,21 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2021 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from .cw_login import current_user
from . import ub
diff --git a/cps/kosync.py b/cps/kosync.py
index bac1edb..148dcfc 100644
--- a/cps/kosync.py
+++ b/cps/kosync.py
@@ -1,5 +1,10 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
"""
KOReader Sync Server Implementation for Calibre-Web-Automated
diff --git a/cps/logger.py b/cps/logger.py
index 9aa076d..2437d54 100755
--- a/cps/logger.py
+++ b/cps/logger.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2019 pwr
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
import sys
diff --git a/cps/main.py b/cps/main.py
index ab22297..ca65c35 100644
--- a/cps/main.py
+++ b/cps/main.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2012-2022 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import sys
diff --git a/cps/metadata_provider/amazon.py b/cps/metadata_provider/amazon.py
index d3c4061..1660585 100755
--- a/cps/metadata_provider/amazon.py
+++ b/cps/metadata_provider/amazon.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2022 quarz12
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import concurrent.futures
import requests
diff --git a/cps/metadata_provider/amazonjp.py b/cps/metadata_provider/amazonjp.py
index 4d31942..de8c7fb 100644
--- a/cps/metadata_provider/amazonjp.py
+++ b/cps/metadata_provider/amazonjp.py
@@ -1,20 +1,10 @@
# -*- coding: utf-8 -*-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2025 quarz12, Hobogrammer
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
import cps.logger as logger
import concurrent.futures
import re
diff --git a/cps/metadata_provider/comicvine.py b/cps/metadata_provider/comicvine.py
index b4d8d34..ac8258d 100755
--- a/cps/metadata_provider/comicvine.py
+++ b/cps/metadata_provider/comicvine.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2021 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
# ComicVine api document: https://comicvine.gamespot.com/api/documentation
from typing import Dict, List, Optional
diff --git a/cps/metadata_provider/douban.py b/cps/metadata_provider/douban.py
index 1dd12d8..9f5469d 100755
--- a/cps/metadata_provider/douban.py
+++ b/cps/metadata_provider/douban.py
@@ -1,20 +1,10 @@
# -*- coding: utf-8 -*-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2022 xlivevil
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
import re
from concurrent import futures
from typing import List, Optional
diff --git a/cps/metadata_provider/google.py b/cps/metadata_provider/google.py
index df9c0dc..3bceca3 100755
--- a/cps/metadata_provider/google.py
+++ b/cps/metadata_provider/google.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2021 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
# Google Books api document: https://developers.google.com/books/docs/v1/using
from typing import Dict, List, Optional
diff --git a/cps/metadata_provider/hardcover.py b/cps/metadata_provider/hardcover.py
index 42152b1..12765c8 100644
--- a/cps/metadata_provider/hardcover.py
+++ b/cps/metadata_provider/hardcover.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2021 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
# Hardcover api document: https://Hardcover.gamespot.com/api/documentation
from typing import Dict, List, Optional
diff --git a/cps/metadata_provider/ibdb.py b/cps/metadata_provider/ibdb.py
index 1371267..a844dc0 100644
--- a/cps/metadata_provider/ibdb.py
+++ b/cps/metadata_provider/ibdb.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2021 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
# Google Books api document: https://developers.google.com/books/docs/v1/using
from typing import Dict, List, Optional
diff --git a/cps/metadata_provider/lubimyczytac.py b/cps/metadata_provider/lubimyczytac.py
index 9d4a99f..bc3b740 100755
--- a/cps/metadata_provider/lubimyczytac.py
+++ b/cps/metadata_provider/lubimyczytac.py
@@ -1,19 +1,10 @@
# -*- coding: utf-8 -*-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2021 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
import datetime
import json
import re
diff --git a/cps/metadata_provider/scholar.py b/cps/metadata_provider/scholar.py
index ad365fa..1427c4f 100755
--- a/cps/metadata_provider/scholar.py
+++ b/cps/metadata_provider/scholar.py
@@ -1,20 +1,10 @@
# -*- coding: utf-8 -*-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2021 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
import itertools
from typing import Dict, List, Optional
from urllib.parse import quote, unquote
diff --git a/cps/oauth.py b/cps/oauth.py
index 0caa61e..842a18f 100755
--- a/cps/oauth.py
+++ b/cps/oauth.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 jim3ma
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from flask import session
diff --git a/cps/oauth_bb.py b/cps/oauth_bb.py
index 7ec813b..bf43e9f 100755
--- a/cps/oauth_bb.py
+++ b/cps/oauth_bb.py
@@ -1,24 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
-# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
-# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
-# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
-# apetresc, nanu-c, mutschler
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import json
from functools import wraps
diff --git a/cps/opds.py b/cps/opds.py
index 485d241..a8d9b4b 100755
--- a/cps/opds.py
+++ b/cps/opds.py
@@ -1,24 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
-# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
-# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
-# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
-# apetresc, nanu-c, mutschler
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import datetime
import json
diff --git a/cps/pagination.py b/cps/pagination.py
index 6f19a99..b366c74 100644
--- a/cps/pagination.py
+++ b/cps/pagination.py
@@ -1,24 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
-# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
-# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
-# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
-# apetresc, nanu-c, mutschler
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from math import ceil
diff --git a/cps/redirect.py b/cps/redirect.py
index 7f504b9..522e29d 100755
--- a/cps/redirect.py
+++ b/cps/redirect.py
@@ -1,4 +1,9 @@
# -*- coding: utf-8 -*-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
# Flask License
#
diff --git a/cps/remotelogin.py b/cps/remotelogin.py
index a31a77b..7e37057 100755
--- a/cps/remotelogin.py
+++ b/cps/remotelogin.py
@@ -1,24 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
-# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
-# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
-# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
-# apetresc, nanu-c, mutschler
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import json
from datetime import datetime
diff --git a/cps/render_template.py b/cps/render_template.py
index f1da125..79d62d1 100644
--- a/cps/render_template.py
+++ b/cps/render_template.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2020 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from flask import render_template, g, abort, request, flash
from flask_babel import gettext as _
diff --git a/cps/reverseproxy.py b/cps/reverseproxy.py
index 4acb8e4..219949d 100755
--- a/cps/reverseproxy.py
+++ b/cps/reverseproxy.py
@@ -1,4 +1,9 @@
# -*- coding: utf-8 -*-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
# Flask License
#
diff --git a/cps/schedule.py b/cps/schedule.py
index d01aab1..99ca9c0 100755
--- a/cps/schedule.py
+++ b/cps/schedule.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2020 mmonkey
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import datetime
diff --git a/cps/search.py b/cps/search.py
index 29dda16..d75033e 100755
--- a/cps/search.py
+++ b/cps/search.py
@@ -1,18 +1,8 @@
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2022 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import json
from datetime import datetime
diff --git a/cps/search_metadata.py b/cps/search_metadata.py
index 726e2a9..a16ebcf 100644
--- a/cps/search_metadata.py
+++ b/cps/search_metadata.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2021 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import concurrent.futures
import importlib
diff --git a/cps/server.py b/cps/server.py
index 41188ac..ad3731d 100755
--- a/cps/server.py
+++ b/cps/server.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2012-2019 janeczku, OzzieIsaacs, andrerfcsantos, idalin
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import sys
import os
diff --git a/cps/services/Metadata.py b/cps/services/Metadata.py
index d975092..2281ec4 100644
--- a/cps/services/Metadata.py
+++ b/cps/services/Metadata.py
@@ -1,20 +1,10 @@
# -*- coding: utf-8 -*-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2021 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
import abc
import dataclasses
import os
diff --git a/cps/services/SyncToken.py b/cps/services/SyncToken.py
index 871d2b2..0ed3992 100755
--- a/cps/services/SyncToken.py
+++ b/cps/services/SyncToken.py
@@ -1,21 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 shavitmichael, OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import sys
from base64 import b64decode, b64encode
diff --git a/cps/services/__init__.py b/cps/services/__init__.py
index 000a6f9..33fc860 100644
--- a/cps/services/__init__.py
+++ b/cps/services/__init__.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2019 pwr
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from .. import logger
diff --git a/cps/services/background_scheduler.py b/cps/services/background_scheduler.py
index 55b12df..be61493 100755
--- a/cps/services/background_scheduler.py
+++ b/cps/services/background_scheduler.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2020 mmonkey
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import atexit
diff --git a/cps/services/gmail.py b/cps/services/gmail.py
index 5b0cdbe..8301518 100755
--- a/cps/services/gmail.py
+++ b/cps/services/gmail.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2021 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os.path
from google_auth_oauthlib.flow import InstalledAppFlow
diff --git a/cps/services/goodreads_support.py b/cps/services/goodreads_support.py
index b3b7bde..31a2f67 100755
--- a/cps/services/goodreads_support.py
+++ b/cps/services/goodreads_support.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, pwr
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import time
from functools import reduce
diff --git a/cps/services/hardcover.py b/cps/services/hardcover.py
index 95f801a..ac651b7 100644
--- a/cps/services/hardcover.py
+++ b/cps/services/hardcover.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, pwr
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from datetime import datetime
import requests
diff --git a/cps/services/simpleldap.py b/cps/services/simpleldap.py
index 3ae048b..4eaa735 100755
--- a/cps/services/simpleldap.py
+++ b/cps/services/simpleldap.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, pwr
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import base64
diff --git a/cps/services/worker.py b/cps/services/worker.py
index dce3da7..cb34b7c 100755
--- a/cps/services/worker.py
+++ b/cps/services/worker.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2020 pwr
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import threading
import abc
diff --git a/cps/shelf.py b/cps/shelf.py
index 1541745..f912d5a 100644
--- a/cps/shelf.py
+++ b/cps/shelf.py
@@ -1,24 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
-# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
-# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
-# ruben-herold, marblepebble, JackED42, SiphonSquirrel,
-# apetresc, nanu-c, mutschler
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import sys
from datetime import datetime, timezone
diff --git a/cps/string_helper.py b/cps/string_helper.py
index d263dd2..fbd9cac 100755
--- a/cps/string_helper.py
+++ b/cps/string_helper.py
@@ -1,20 +1,10 @@
# -*- coding: utf-8 -*-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2024 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
import re
diff --git a/cps/subproc_wrapper.py b/cps/subproc_wrapper.py
index 187b2cb..a2498f1 100755
--- a/cps/subproc_wrapper.py
+++ b/cps/subproc_wrapper.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import sys
import os
diff --git a/cps/tasks/__init__.py b/cps/tasks/__init__.py
index e69de29..a158d39 100755
--- a/cps/tasks/__init__.py
+++ b/cps/tasks/__init__.py
@@ -0,0 +1,6 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
diff --git a/cps/tasks/clean.py b/cps/tasks/clean.py
index 1a1681b..620cec8 100755
--- a/cps/tasks/clean.py
+++ b/cps/tasks/clean.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2023 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import datetime
diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py
index 08eb63c..df97a07 100755
--- a/cps/tasks/convert.py
+++ b/cps/tasks/convert.py
@@ -1,351 +1,340 @@
-# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2020 pwr
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-import os
-import re
-import glob
-from shutil import copyfile, copyfileobj
-from markupsafe import escape
-from time import time
-from uuid import uuid4
-
-from sqlalchemy.exc import SQLAlchemyError
-from flask_babel import lazy_gettext as N_
-
-from cps.services.worker import CalibreTask
-from cps import db
-from cps import logger, config
-from cps.subproc_wrapper import process_open
-from flask_babel import gettext as _
-from cps.kobo_sync_status import remove_synced_book
-from cps.ub import init_db_thread
-from cps.file_helper import get_temp_dir
-
-from cps.tasks.mail import TaskEmail
-from cps import gdriveutils, helper
-from cps.constants import SUPPORTED_CALIBRE_BINARIES
-from cps.string_helper import strip_whitespaces
-
-log = logger.create()
-
-current_milli_time = lambda: int(round(time() * 1000))
-
-
-class TaskConvert(CalibreTask):
- def __init__(self, file_path, book_id, task_message, settings, ereader_mail, user=None):
- super(TaskConvert, self).__init__(task_message)
- self.worker_thread = None
- self.file_path = file_path
- self.book_id = book_id
- self.title = ""
- self.settings = settings
- self.ereader_mail = ereader_mail
- self.user = user
-
- self.results = dict()
-
- def run(self, worker_thread):
- self.worker_thread = worker_thread
- if config.config_use_google_drive:
- worker_db = db.CalibreDB(expire_on_commit=False, init=True)
- cur_book = worker_db.get_book(self.book_id)
- self.title = cur_book.title
- data = worker_db.get_book_format(self.book_id, self.settings['old_book_format'])
- df = gdriveutils.getFileFromEbooksFolder(cur_book.path,
- data.name + "." + self.settings['old_book_format'].lower())
- df_cover = gdriveutils.getFileFromEbooksFolder(cur_book.path, "cover.jpg")
- if df:
- datafile_cover = None
- datafile = os.path.join(config.get_book_path(),
- cur_book.path,
- data.name + "." + self.settings['old_book_format'].lower())
- if df_cover:
- datafile_cover = os.path.join(config.get_book_path(),
- cur_book.path, "cover.jpg")
- if not os.path.exists(os.path.join(config.get_book_path(), cur_book.path)):
- os.makedirs(os.path.join(config.get_book_path(), cur_book.path))
- df.GetContentFile(datafile)
- if df_cover:
- df_cover.GetContentFile(datafile_cover)
- worker_db.session.close()
- else:
- # ToDo Include cover in error handling
- error_message = _("%(format)s not found on Google Drive: %(fn)s",
- format=self.settings['old_book_format'],
- fn=data.name + "." + self.settings['old_book_format'].lower())
- worker_db.session.close()
- return self._handleError(error_message)
-
- filename = self._convert_ebook_format()
- if config.config_use_google_drive:
- os.remove(self.file_path + '.' + self.settings['old_book_format'].lower())
- if df_cover:
- os.remove(os.path.join(config.config_calibre_dir, cur_book.path, "cover.jpg"))
-
- if filename:
- if config.config_use_google_drive:
- # Upload files to gdrive
- gdriveutils.updateGdriveCalibreFromLocal()
- self._handleSuccess()
- if self.ereader_mail:
- # if we're sending to E-Reader after converting, create a one-off task and run it immediately
- # todo: figure out how to incorporate this into the progress
- try:
- EmailText = N_(u"%(book)s send to E-Reader", book=escape(self.title))
- for email in self.ereader_mail.split(','):
- email = strip_whitespaces(email)
- worker_thread.add(self.user, TaskEmail(self.settings['subject'],
- self.results["path"],
- filename,
- self.settings,
- email,
- EmailText,
- self.settings['body'],
- id=self.book_id,
- internal=True)
- )
- except Exception as ex:
- return self._handleError(str(ex))
-
- def _convert_ebook_format(self):
- error_message = None
- local_db = db.CalibreDB(expire_on_commit=False, init=True)
- file_path = self.file_path
- book_id = self.book_id
- format_old_ext = '.' + self.settings['old_book_format'].lower()
- format_new_ext = '.' + self.settings['new_book_format'].lower()
-
- # check to see if destination format already exists - or if book is in database
- # if it does - mark the conversion task as complete and return a success
- # this will allow to send to E-Reader workflow to continue to work
- if os.path.isfile(file_path + format_new_ext) or\
- local_db.get_book_format(self.book_id, self.settings['new_book_format']):
- log.info("Book id %d already converted to %s", book_id, format_new_ext)
- cur_book = local_db.get_book(book_id)
- self.title = cur_book.title
- self.results['path'] = cur_book.path
- self.results['title'] = self.title
- new_format = local_db.session.query(db.Data).filter(db.Data.book == book_id)\
- .filter(db.Data.format == self.settings['new_book_format'].upper()).one_or_none()
- if not new_format:
- new_format = db.Data(name=os.path.basename(file_path),
- book_format=self.settings['new_book_format'].upper(),
- book=book_id, uncompressed_size=os.path.getsize(file_path + format_new_ext))
- try:
- local_db.session.merge(new_format)
- local_db.session.commit()
- except SQLAlchemyError as e:
- local_db.session.rollback()
- log.error("Database error: %s", e)
- local_db.session.close()
- self._handleError(N_("Oops! Database Error: %(error)s.", error=e))
- return
- self._handleSuccess()
- local_db.session.close()
- return os.path.basename(file_path + format_new_ext)
- else:
- log.info("Book id %d - target format of %s does not exist. Moving forward with convert.",
- book_id,
- format_new_ext)
-
- if config.config_kepubifypath and format_old_ext == '.epub' and format_new_ext == '.kepub':
- check, error_message = self._convert_kepubify(file_path,
- format_old_ext,
- format_new_ext)
- else:
- # check if calibre converter-executable is existing
- if not os.path.exists(config.config_converterpath):
- self._handleError(N_("Calibre ebook-convert %(tool)s not found", tool=config.config_converterpath))
- return
- has_cover = local_db.get_book(book_id).has_cover
- check, error_message = self._convert_calibre(file_path, format_old_ext, format_new_ext, has_cover)
-
- if check == 0:
- cur_book = local_db.get_book(book_id)
- if os.path.isfile(file_path + format_new_ext):
- new_format = local_db.session.query(db.Data).filter(db.Data.book == book_id) \
- .filter(db.Data.format == self.settings['new_book_format'].upper()).one_or_none()
- if not new_format:
- new_format = db.Data(name=cur_book.data[0].name,
- book_format=self.settings['new_book_format'].upper(),
- book=book_id, uncompressed_size=os.path.getsize(file_path + format_new_ext))
- try:
- local_db.session.merge(new_format)
- local_db.session.commit()
- if self.settings['new_book_format'].upper() in ['KEPUB', 'EPUB', 'EPUB3']:
- ub_session = init_db_thread()
- remove_synced_book(book_id, True, ub_session)
- ub_session.close()
- except SQLAlchemyError as e:
- local_db.session.rollback()
- log.error("Database error: %s", e)
- local_db.session.close()
- self._handleError(error_message)
- return
- self.results['path'] = cur_book.path
- self.title = cur_book.title
- self.results['title'] = self.title
- if not config.config_use_google_drive:
- self._handleSuccess()
- return os.path.basename(file_path + format_new_ext)
- else:
- error_message = N_('%(format)s format not found on disk', format=format_new_ext.upper())
- local_db.session.close()
- log.info("ebook converter failed with error while converting book")
- if not error_message:
- error_message = N_('Ebook converter failed with unknown error')
- else:
- log.error(error_message)
- self._handleError(error_message)
- return
-
- def _convert_kepubify(self, file_path, format_old_ext, format_new_ext):
- if config.config_embed_metadata and config.config_binariesdir:
- tmp_dir, temp_file_name = helper.do_calibre_export(self.book_id, format_old_ext[1:])
- filename = os.path.join(tmp_dir, temp_file_name + format_old_ext)
- temp_file_path = tmp_dir
- else:
- filename = file_path + format_old_ext
- temp_file_path = os.path.dirname(file_path)
- quotes = [1, 3]
- command = [config.config_kepubifypath, filename, '-o', temp_file_path, '-i']
- try:
- p = process_open(command, quotes)
- except OSError as e:
- return 1, N_("Kepubify-converter failed: %(error)s", error=e)
- self.progress = 0.01
- while True:
- nextline = p.stdout.readlines()
- nextline = [x.strip('\n') for x in nextline if x != '\n']
- for line in nextline:
- log.debug(line)
- if p.poll() is not None:
- break
-
- # process returncode
- check = p.returncode
-
- # move file
- if check == 0:
- converted_file = glob.glob(glob.escape(os.path.splitext(filename)[0]) + "*.kepub.epub")
- if len(converted_file) == 1:
- copyfile(converted_file[0], (file_path + format_new_ext))
- os.unlink(converted_file[0])
- else:
- return 1, N_("Converted file not found or more than one file in folder %(folder)s",
- folder=os.path.dirname(file_path))
- return check, None
-
- def _convert_calibre(self, file_path, format_old_ext, format_new_ext, has_cover):
- path_tmp_opf = None
- try:
- # path_tmp_opf = self._embed_metadata()
- if config.config_embed_metadata:
- quotes = [5]
- tmp_dir = get_temp_dir()
- calibredb_binarypath = os.path.join(config.config_binariesdir, SUPPORTED_CALIBRE_BINARIES["calibredb"])
- my_env = os.environ.copy()
- if config.config_calibre_split:
- my_env['CALIBRE_OVERRIDE_DATABASE_PATH'] = os.path.join(config.config_calibre_dir, "metadata.db")
- library_path = config.config_calibre_split_dir
- else:
- library_path = config.config_calibre_dir
-
- opf_command = [calibredb_binarypath, 'show_metadata', '--as-opf', str(self.book_id),
- '--with-library', library_path]
- p = process_open(opf_command, quotes, my_env, newlines=False)
- lines = list()
- while p.poll() is None:
- lines.append(p.stdout.readline())
- check = p.returncode
- calibre_traceback = p.stderr.readlines()
- if check == 0:
- path_tmp_opf = os.path.join(tmp_dir, "metadata_" + str(uuid4()) + ".opf")
- with open(path_tmp_opf, 'wb') as fd:
- fd.write(b''.join(lines))
- else:
- error_message = ""
- for ele in calibre_traceback:
- if not ele.startswith('Traceback') and not ele.startswith(' File'):
- error_message = N_("Calibre failed with error: %(error)s", error=ele)
- return check, error_message
- quotes = [1, 2]
- quotes_index = 3
- command = [config.config_converterpath, (file_path + format_old_ext),
- (file_path + format_new_ext)]
- if config.config_embed_metadata:
- quotes.append(4)
- quotes_index = 5
- command.extend(['--from-opf', path_tmp_opf])
- if has_cover:
- quotes.append(6)
- command.extend(['--cover', os.path.join(os.path.dirname(file_path), 'cover.jpg')])
- quotes_index = 7
- if config.config_calibre:
- parameters = re.findall(r"(--[\w-]+)(?:(\s(?:(\".+\")|(?:.+?)))(?:\s|$))?",
- config.config_calibre, re.IGNORECASE | re.UNICODE)
- if parameters:
- for param in parameters:
- command.append(strip_whitespaces(param[0]))
- quotes_index += 1
- if param[1] != "":
- parsed = strip_whitespaces(param[1]).strip("\"")
- command.append(parsed)
- quotes.append(quotes_index)
- quotes_index += 1
- p = process_open(command, quotes, newlines=False)
- except OSError as e:
- return 1, N_("Ebook-converter failed: %(error)s", error=e)
-
- while p.poll() is None:
- nextline = p.stdout.readline()
- if isinstance(nextline, bytes):
- nextline = nextline.decode('utf-8', errors="ignore").strip('\r\n')
- if nextline:
- log.debug(nextline)
- # parse progress string from calibre-converter
- progress = re.search(r"(\d+)%\s.*", nextline)
- if progress:
- self.progress = int(progress.group(1)) / 100
- if config.config_use_google_drive:
- self.progress *= 0.9
-
- # process returncode
- check = p.returncode
- calibre_traceback = p.stderr.readlines()
- error_message = ""
- for ele in calibre_traceback:
- ele = ele.decode('utf-8', errors="ignore").strip('\n')
- log.debug(ele)
- if not ele.startswith('Traceback') and not ele.startswith(' File'):
- error_message = N_("Calibre failed with error: %(error)s", error=ele)
- return check, error_message
-
- @property
- def name(self):
- return N_("Convert")
-
- def __str__(self):
- if self.ereader_mail:
- return "Convert Book {} and mail it to {}".format(self.book_id, self.ereader_mail)
- else:
- return "Convert Book {}".format(self.book_id)
-
- @property
- def is_cancellable(self):
- return False
+# -*- coding: utf-8 -*-
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
+import os
+import re
+import glob
+from shutil import copyfile, copyfileobj
+from markupsafe import escape
+from time import time
+from uuid import uuid4
+
+from sqlalchemy.exc import SQLAlchemyError
+from flask_babel import lazy_gettext as N_
+
+from cps.services.worker import CalibreTask
+from cps import db
+from cps import logger, config
+from cps.subproc_wrapper import process_open
+from flask_babel import gettext as _
+from cps.kobo_sync_status import remove_synced_book
+from cps.ub import init_db_thread
+from cps.file_helper import get_temp_dir
+
+from cps.tasks.mail import TaskEmail
+from cps import gdriveutils, helper
+from cps.constants import SUPPORTED_CALIBRE_BINARIES
+from cps.string_helper import strip_whitespaces
+
+log = logger.create()
+
+current_milli_time = lambda: int(round(time() * 1000))
+
+
+class TaskConvert(CalibreTask):
+ def __init__(self, file_path, book_id, task_message, settings, ereader_mail, user=None):
+ super(TaskConvert, self).__init__(task_message)
+ self.worker_thread = None
+ self.file_path = file_path
+ self.book_id = book_id
+ self.title = ""
+ self.settings = settings
+ self.ereader_mail = ereader_mail
+ self.user = user
+
+ self.results = dict()
+
+ def run(self, worker_thread):
+ self.worker_thread = worker_thread
+ if config.config_use_google_drive:
+ worker_db = db.CalibreDB(expire_on_commit=False, init=True)
+ cur_book = worker_db.get_book(self.book_id)
+ self.title = cur_book.title
+ data = worker_db.get_book_format(self.book_id, self.settings['old_book_format'])
+ df = gdriveutils.getFileFromEbooksFolder(cur_book.path,
+ data.name + "." + self.settings['old_book_format'].lower())
+ df_cover = gdriveutils.getFileFromEbooksFolder(cur_book.path, "cover.jpg")
+ if df:
+ datafile_cover = None
+ datafile = os.path.join(config.get_book_path(),
+ cur_book.path,
+ data.name + "." + self.settings['old_book_format'].lower())
+ if df_cover:
+ datafile_cover = os.path.join(config.get_book_path(),
+ cur_book.path, "cover.jpg")
+ if not os.path.exists(os.path.join(config.get_book_path(), cur_book.path)):
+ os.makedirs(os.path.join(config.get_book_path(), cur_book.path))
+ df.GetContentFile(datafile)
+ if df_cover:
+ df_cover.GetContentFile(datafile_cover)
+ worker_db.session.close()
+ else:
+ # ToDo Include cover in error handling
+ error_message = _("%(format)s not found on Google Drive: %(fn)s",
+ format=self.settings['old_book_format'],
+ fn=data.name + "." + self.settings['old_book_format'].lower())
+ worker_db.session.close()
+ return self._handleError(error_message)
+
+ filename = self._convert_ebook_format()
+ if config.config_use_google_drive:
+ os.remove(self.file_path + '.' + self.settings['old_book_format'].lower())
+ if df_cover:
+ os.remove(os.path.join(config.config_calibre_dir, cur_book.path, "cover.jpg"))
+
+ if filename:
+ if config.config_use_google_drive:
+ # Upload files to gdrive
+ gdriveutils.updateGdriveCalibreFromLocal()
+ self._handleSuccess()
+ if self.ereader_mail:
+ # if we're sending to E-Reader after converting, create a one-off task and run it immediately
+ # todo: figure out how to incorporate this into the progress
+ try:
+ EmailText = N_(u"%(book)s send to E-Reader", book=escape(self.title))
+ for email in self.ereader_mail.split(','):
+ email = strip_whitespaces(email)
+ worker_thread.add(self.user, TaskEmail(self.settings['subject'],
+ self.results["path"],
+ filename,
+ self.settings,
+ email,
+ EmailText,
+ self.settings['body'],
+ id=self.book_id,
+ internal=True)
+ )
+ except Exception as ex:
+ return self._handleError(str(ex))
+
+ def _convert_ebook_format(self):
+ error_message = None
+ local_db = db.CalibreDB(expire_on_commit=False, init=True)
+ file_path = self.file_path
+ book_id = self.book_id
+ format_old_ext = '.' + self.settings['old_book_format'].lower()
+ format_new_ext = '.' + self.settings['new_book_format'].lower()
+
+ # check to see if destination format already exists - or if book is in database
+ # if it does - mark the conversion task as complete and return a success
+ # this will allow to send to E-Reader workflow to continue to work
+ if os.path.isfile(file_path + format_new_ext) or\
+ local_db.get_book_format(self.book_id, self.settings['new_book_format']):
+ log.info("Book id %d already converted to %s", book_id, format_new_ext)
+ cur_book = local_db.get_book(book_id)
+ self.title = cur_book.title
+ self.results['path'] = cur_book.path
+ self.results['title'] = self.title
+ new_format = local_db.session.query(db.Data).filter(db.Data.book == book_id)\
+ .filter(db.Data.format == self.settings['new_book_format'].upper()).one_or_none()
+ if not new_format:
+ new_format = db.Data(name=os.path.basename(file_path),
+ book_format=self.settings['new_book_format'].upper(),
+ book=book_id, uncompressed_size=os.path.getsize(file_path + format_new_ext))
+ try:
+ local_db.session.merge(new_format)
+ local_db.session.commit()
+ except SQLAlchemyError as e:
+ local_db.session.rollback()
+ log.error("Database error: %s", e)
+ local_db.session.close()
+ self._handleError(N_("Oops! Database Error: %(error)s.", error=e))
+ return
+ self._handleSuccess()
+ local_db.session.close()
+ return os.path.basename(file_path + format_new_ext)
+ else:
+ log.info("Book id %d - target format of %s does not exist. Moving forward with convert.",
+ book_id,
+ format_new_ext)
+
+ if config.config_kepubifypath and format_old_ext == '.epub' and format_new_ext == '.kepub':
+ check, error_message = self._convert_kepubify(file_path,
+ format_old_ext,
+ format_new_ext)
+ else:
+ # check if calibre converter-executable is existing
+ if not os.path.exists(config.config_converterpath):
+ self._handleError(N_("Calibre ebook-convert %(tool)s not found", tool=config.config_converterpath))
+ return
+ has_cover = local_db.get_book(book_id).has_cover
+ check, error_message = self._convert_calibre(file_path, format_old_ext, format_new_ext, has_cover)
+
+ if check == 0:
+ cur_book = local_db.get_book(book_id)
+ if os.path.isfile(file_path + format_new_ext):
+ new_format = local_db.session.query(db.Data).filter(db.Data.book == book_id) \
+ .filter(db.Data.format == self.settings['new_book_format'].upper()).one_or_none()
+ if not new_format:
+ new_format = db.Data(name=cur_book.data[0].name,
+ book_format=self.settings['new_book_format'].upper(),
+ book=book_id, uncompressed_size=os.path.getsize(file_path + format_new_ext))
+ try:
+ local_db.session.merge(new_format)
+ local_db.session.commit()
+ if self.settings['new_book_format'].upper() in ['KEPUB', 'EPUB', 'EPUB3']:
+ ub_session = init_db_thread()
+ remove_synced_book(book_id, True, ub_session)
+ ub_session.close()
+ except SQLAlchemyError as e:
+ local_db.session.rollback()
+ log.error("Database error: %s", e)
+ local_db.session.close()
+ self._handleError(error_message)
+ return
+ self.results['path'] = cur_book.path
+ self.title = cur_book.title
+ self.results['title'] = self.title
+ if not config.config_use_google_drive:
+ self._handleSuccess()
+ return os.path.basename(file_path + format_new_ext)
+ else:
+ error_message = N_('%(format)s format not found on disk', format=format_new_ext.upper())
+ local_db.session.close()
+ log.info("ebook converter failed with error while converting book")
+ if not error_message:
+ error_message = N_('Ebook converter failed with unknown error')
+ else:
+ log.error(error_message)
+ self._handleError(error_message)
+ return
+
+ def _convert_kepubify(self, file_path, format_old_ext, format_new_ext):
+ if config.config_embed_metadata and config.config_binariesdir:
+ tmp_dir, temp_file_name = helper.do_calibre_export(self.book_id, format_old_ext[1:])
+ filename = os.path.join(tmp_dir, temp_file_name + format_old_ext)
+ temp_file_path = tmp_dir
+ else:
+ filename = file_path + format_old_ext
+ temp_file_path = os.path.dirname(file_path)
+ quotes = [1, 3]
+ command = [config.config_kepubifypath, filename, '-o', temp_file_path, '-i']
+ try:
+ p = process_open(command, quotes)
+ except OSError as e:
+ return 1, N_("Kepubify-converter failed: %(error)s", error=e)
+ self.progress = 0.01
+ while True:
+ nextline = p.stdout.readlines()
+ nextline = [x.strip('\n') for x in nextline if x != '\n']
+ for line in nextline:
+ log.debug(line)
+ if p.poll() is not None:
+ break
+
+ # process returncode
+ check = p.returncode
+
+ # move file
+ if check == 0:
+ converted_file = glob.glob(glob.escape(os.path.splitext(filename)[0]) + "*.kepub.epub")
+ if len(converted_file) == 1:
+ copyfile(converted_file[0], (file_path + format_new_ext))
+ os.unlink(converted_file[0])
+ else:
+ return 1, N_("Converted file not found or more than one file in folder %(folder)s",
+ folder=os.path.dirname(file_path))
+ return check, None
+
+ def _convert_calibre(self, file_path, format_old_ext, format_new_ext, has_cover):
+ path_tmp_opf = None
+ try:
+ # path_tmp_opf = self._embed_metadata()
+ if config.config_embed_metadata:
+ quotes = [5]
+ tmp_dir = get_temp_dir()
+ calibredb_binarypath = os.path.join(config.config_binariesdir, SUPPORTED_CALIBRE_BINARIES["calibredb"])
+ my_env = os.environ.copy()
+ if config.config_calibre_split:
+ my_env['CALIBRE_OVERRIDE_DATABASE_PATH'] = os.path.join(config.config_calibre_dir, "metadata.db")
+ library_path = config.config_calibre_split_dir
+ else:
+ library_path = config.config_calibre_dir
+
+ opf_command = [calibredb_binarypath, 'show_metadata', '--as-opf', str(self.book_id),
+ '--with-library', library_path]
+ p = process_open(opf_command, quotes, my_env, newlines=False)
+ lines = list()
+ while p.poll() is None:
+ lines.append(p.stdout.readline())
+ check = p.returncode
+ calibre_traceback = p.stderr.readlines()
+ if check == 0:
+ path_tmp_opf = os.path.join(tmp_dir, "metadata_" + str(uuid4()) + ".opf")
+ with open(path_tmp_opf, 'wb') as fd:
+ fd.write(b''.join(lines))
+ else:
+ error_message = ""
+ for ele in calibre_traceback:
+ if not ele.startswith('Traceback') and not ele.startswith(' File'):
+ error_message = N_("Calibre failed with error: %(error)s", error=ele)
+ return check, error_message
+ quotes = [1, 2]
+ quotes_index = 3
+ command = [config.config_converterpath, (file_path + format_old_ext),
+ (file_path + format_new_ext)]
+ if config.config_embed_metadata:
+ quotes.append(4)
+ quotes_index = 5
+ command.extend(['--from-opf', path_tmp_opf])
+ if has_cover:
+ quotes.append(6)
+ command.extend(['--cover', os.path.join(os.path.dirname(file_path), 'cover.jpg')])
+ quotes_index = 7
+ if config.config_calibre:
+ parameters = re.findall(r"(--[\w-]+)(?:(\s(?:(\".+\")|(?:.+?)))(?:\s|$))?",
+ config.config_calibre, re.IGNORECASE | re.UNICODE)
+ if parameters:
+ for param in parameters:
+ command.append(strip_whitespaces(param[0]))
+ quotes_index += 1
+ if param[1] != "":
+ parsed = strip_whitespaces(param[1]).strip("\"")
+ command.append(parsed)
+ quotes.append(quotes_index)
+ quotes_index += 1
+ p = process_open(command, quotes, newlines=False)
+ except OSError as e:
+ return 1, N_("Ebook-converter failed: %(error)s", error=e)
+
+ while p.poll() is None:
+ nextline = p.stdout.readline()
+ if isinstance(nextline, bytes):
+ nextline = nextline.decode('utf-8', errors="ignore").strip('\r\n')
+ if nextline:
+ log.debug(nextline)
+ # parse progress string from calibre-converter
+ progress = re.search(r"(\d+)%\s.*", nextline)
+ if progress:
+ self.progress = int(progress.group(1)) / 100
+ if config.config_use_google_drive:
+ self.progress *= 0.9
+
+ # process returncode
+ check = p.returncode
+ calibre_traceback = p.stderr.readlines()
+ error_message = ""
+ for ele in calibre_traceback:
+ ele = ele.decode('utf-8', errors="ignore").strip('\n')
+ log.debug(ele)
+ if not ele.startswith('Traceback') and not ele.startswith(' File'):
+ error_message = N_("Calibre failed with error: %(error)s", error=ele)
+ return check, error_message
+
+ @property
+ def name(self):
+ return N_("Convert")
+
+ def __str__(self):
+ if self.ereader_mail:
+ return "Convert Book {} and mail it to {}".format(self.book_id, self.ereader_mail)
+ else:
+ return "Convert Book {}".format(self.book_id)
+
+ @property
+ def is_cancellable(self):
+ return False
diff --git a/cps/tasks/database.py b/cps/tasks/database.py
index c9c30d4..d5fb545 100755
--- a/cps/tasks/database.py
+++ b/cps/tasks/database.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2020 mmonkey
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from flask_babel import lazy_gettext as N_
diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py
index 49cd913..58504f8 100755
--- a/cps/tasks/mail.py
+++ b/cps/tasks/mail.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2020 pwr
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
import smtplib
diff --git a/cps/tasks/metadata_backup.py b/cps/tasks/metadata_backup.py
index 5e0bb96..f538f0d 100755
--- a/cps/tasks/metadata_backup.py
+++ b/cps/tasks/metadata_backup.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2020 monkey
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
from lxml import etree
diff --git a/cps/tasks/thumbnail.py b/cps/tasks/thumbnail.py
index a7658c0..240234c 100755
--- a/cps/tasks/thumbnail.py
+++ b/cps/tasks/thumbnail.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2020 monkey
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
from shutil import copyfile, copyfileobj
diff --git a/cps/tasks/upload.py b/cps/tasks/upload.py
index a74fc2f..e9329bf 100755
--- a/cps/tasks/upload.py
+++ b/cps/tasks/upload.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2020 pwr
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from datetime import datetime
diff --git a/cps/tasks_status.py b/cps/tasks_status.py
index 268200d..638089c 100755
--- a/cps/tasks_status.py
+++ b/cps/tasks_status.py
@@ -1,18 +1,8 @@
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2022 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from markupsafe import escape
diff --git a/cps/tornado_wsgi.py b/cps/tornado_wsgi.py
index f2aae6d..525dfb5 100755
--- a/cps/tornado_wsgi.py
+++ b/cps/tornado_wsgi.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2022 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from tornado.wsgi import WSGIContainer
import tornado
diff --git a/cps/ub.py b/cps/ub.py
index 8047fe7..ede72bd 100644
--- a/cps/ub.py
+++ b/cps/ub.py
@@ -1,21 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2012-2019 mutschler, jkrehm, cervinko, janeczku, OzzieIsaacs, csitko
-# ok11, issmirnov, idalin
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import atexit
import os
diff --git a/cps/updater.py b/cps/updater.py
index e33b80f..63a82e3 100755
--- a/cps/updater.py
+++ b/cps/updater.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2019 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import sys
import os
diff --git a/cps/uploader.py b/cps/uploader.py
index 79016e4..af54909 100755
--- a/cps/uploader.py
+++ b/cps/uploader.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2012-2019 lemmsh cervinko Kennyl matthazinski OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
import os
import hashlib
diff --git a/cps/usermanagement.py b/cps/usermanagement.py
index 31c37a9..e5f13a7 100755
--- a/cps/usermanagement.py
+++ b/cps/usermanagement.py
@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
-
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2018-2020 OzzieIsaacs
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
from functools import wraps
diff --git a/cps/web.py b/cps/web.py
index eab65ed..3c4609f 100644
--- a/cps/web.py
+++ b/cps/web.py
@@ -1,4 +1,10 @@
-# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
+)
# Copyright (C) 2018-2019 OzzieIsaacs, cervinko, jkrehm, bodybybuddha, ok11,
# andy29485, idalin, Kyosfonica, wuqi, Kennyl, lemmsh,
# falgh1, grunjol, csitko, ytils, xybydy, trasba, vrabe,
diff --git a/generate_contributors.py b/generate_contributors.py
new file mode 100644
index 0000000..fff4f52
--- /dev/null
+++ b/generate_contributors.py
@@ -0,0 +1,219 @@
+#!/usr/bin/env python3
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
+"""
+Generate (or refresh) a CONTRIBUTORS file by querying the GitHub API for:
+ * Upstream Calibre-Web repo (janeczku/calibre-web)
+ * Fork Calibre-Web Automated repo (crocodilestick/calibre-web-automated)
+
+Features:
+ * Handles pagination (per_page=100) until all contributors are fetched.
+ * Includes anonymous contributors (?anon=1).
+ * Aggregates contribution counts per displayed name.
+ * Separates upstream and fork sections for clarity & license attribution.
+ * Uses GITHUB_TOKEN if present (recommended to avoid low unauthenticated rate limits).
+ * Idempotent: rewrites CONTRIBUTORS file in project root.
+
+Usage:
+ export GITHUB_TOKEN=ghp_xxx # optional but recommended
+ python generate_contributors.py
+
+Optional arguments:
+ --upstream owner/repo (default janeczku/calibre-web)
+ --fork owner/repo (default crocodilestick/calibre-web-automated)
+ --output FILENAME (default CONTRIBUTORS)
+ --include-avatars (adds avatar URLs as HTML comments)
+
+The script purposely avoids adding emails (privacy) and does not try to
+resolve real names beyond what the contributors endpoint provides.
+
+Requires: requests (falls back to stdlib urllib if unavailable, with reduced functionality)
+"""
+from __future__ import annotations
+
+import argparse
+import datetime
+import json
+import os
+import sys
+import time
+from typing import Dict, List, Tuple
+
+try:
+ import requests # type: ignore
+except ImportError: # Lightweight fallback (no pagination link parsing beyond basic)
+ requests = None # type: ignore
+ import urllib.request
+ import urllib.error
+
+DEFAULT_UPSTREAM = "janeczku/calibre-web"
+DEFAULT_FORK = "crocodilestick/calibre-web-automated"
+START_YEAR_FORK = 2024
+
+API_BASE = "https://api.github.com"
+PER_PAGE = 100
+
+
+def parse_args() -> argparse.Namespace:
+ p = argparse.ArgumentParser(description="Generate CONTRIBUTORS file from GitHub API")
+ p.add_argument("--upstream", default=DEFAULT_UPSTREAM, help="Upstream owner/repo")
+ p.add_argument("--fork", default=DEFAULT_FORK, help="Fork owner/repo")
+ p.add_argument("--output", default="CONTRIBUTORS", help="Output filename")
+ p.add_argument("--include-avatars", action="store_true", help="Include avatar URLs as comments")
+ p.add_argument("--timeout", type=float, default=15.0, help="HTTP timeout seconds")
+ p.add_argument("--retries", type=int, default=3, help="Retry attempts on transient errors")
+ return p.parse_args()
+
+
+def gh_headers() -> Dict[str, str]:
+ token = os.getenv("GITHUB_TOKEN")
+ h = {"Accept": "application/vnd.github+json"}
+ if token:
+ h["Authorization"] = f"Bearer {token}"
+ return h
+
+
+def http_get(url: str, timeout: float) -> Tuple[int, Dict[str, str], str]:
+ """Returns (status_code, headers, text)."""
+ if requests:
+ resp = requests.get(url, headers=gh_headers(), timeout=timeout)
+ return resp.status_code, dict(resp.headers), resp.text
+ else:
+ req = urllib.request.Request(url, headers=gh_headers())
+ try:
+ with urllib.request.urlopen(req, timeout=timeout) as r: # type: ignore
+ return r.status, dict(r.headers), r.read().decode()
+ except urllib.error.HTTPError as e: # type: ignore
+ return e.code, dict(e.headers), e.read().decode(errors="ignore")
+
+
+def parse_link_header(headers: Dict[str, str]) -> Dict[str, str]:
+ link = headers.get("Link") or headers.get("link")
+ res: Dict[str, str] = {}
+ if not link:
+ return res
+ parts = link.split(",")
+ for part in parts:
+ segs = part.split(";")
+ if len(segs) < 2:
+ continue
+ url_part = segs[0].strip().lstrip("<").rstrip(">")
+ rel = None
+ for s in segs[1:]:
+ if "rel=" in s:
+ rel = s.split("=", 1)[1].strip().strip('"')
+ if rel:
+ res[rel] = url_part
+ return res
+
+
+def fetch_all_contributors(repo: str, timeout: float, retries: int) -> List[Dict]:
+ owner_repo = repo
+ url = f"{API_BASE}/repos/{owner_repo}/contributors?per_page={PER_PAGE}&anon=1"
+ all_items: List[Dict] = []
+ while url:
+ attempt = 0
+ while attempt <= retries:
+ status, headers, text = http_get(url, timeout)
+ if status == 200:
+ break
+ attempt += 1
+ if status in (403, 429) and attempt <= retries:
+ # simple backoff on rate limiting
+ time.sleep(2 * attempt)
+ else:
+ sys.stderr.write(f"Warning: failed to fetch {url} (status {status})\n")
+ return all_items
+ try:
+ data = json.loads(text)
+ if not isinstance(data, list): # Unexpected structure
+ sys.stderr.write(f"Warning: unexpected JSON structure for {repo}\n")
+ return all_items
+ all_items.extend(data)
+ except json.JSONDecodeError:
+ sys.stderr.write(f"Warning: JSON decode error for {repo}\n")
+ return all_items
+ links = parse_link_header(headers)
+ url = links.get("next")
+ return all_items
+
+
+def aggregate(items: List[Dict]) -> Dict[str, Dict[str, int]]:
+ """Return mapping display_name -> {'contributions': int, 'anonymous': 0/1}."""
+ agg: Dict[str, Dict[str, int]] = {}
+ for c in items:
+ login = c.get("login")
+ is_anon = 1 if (c.get("type") == "Anonymous" or login is None) else 0
+ name = login or c.get("name") or "anonymous"
+ entry = agg.setdefault(name, {"contributions": 0, "anonymous": is_anon})
+ entry["contributions"] += int(c.get("contributions", 0))
+ # If any occurrence is anonymous, keep flag
+ entry["anonymous"] = max(entry["anonymous"], is_anon)
+ return agg
+
+
+def format_section(title: str, repo: str, agg: Dict[str, Dict[str, int]], include_avatars: bool, raw_items: List[Dict]) -> str:
+ lines = [f"# {title} ({repo})", ""]
+ # Map name -> avatar (first occurrence)
+ avatar_map: Dict[str, str] = {}
+ if include_avatars:
+ for c in raw_items:
+ key = c.get("login") or c.get("name") or "anonymous"
+ if key not in avatar_map and c.get("avatar_url"):
+ avatar_map[key] = c["avatar_url"]
+ for name, data in sorted(agg.items(), key=lambda x: (-x[1]["contributions"], x[0].lower())):
+ anon_mark = " (anon)" if data["anonymous"] else ""
+ line = f"- {name}{anon_mark} ({data['contributions']} commits)"
+ if include_avatars and name in avatar_map:
+ line += f" "
+ lines.append(line)
+ lines.append("")
+ return "\n".join(lines)
+
+
+def build_contributors(upstream: str, fork: str, include_avatars: bool, timeout: float, retries: int) -> str:
+ upstream_items = fetch_all_contributors(upstream, timeout, retries)
+ fork_items = fetch_all_contributors(fork, timeout, retries)
+
+ upstream_agg = aggregate(upstream_items)
+ fork_agg = aggregate(fork_items)
+
+ year_now = datetime.date.today().year
+
+ header = [
+ "CONTRIBUTORS",
+ "", # blank line
+ "This file is automatically generated. DO NOT EDIT MANUALLY.",
+ f"Generated on: {datetime.datetime.utcnow().isoformat()}Z",
+ "", # blank line
+ f"Upstream project: https://github.com/{upstream}",
+ f"Fork project (Calibre-Web Automated, since {START_YEAR_FORK}): https://github.com/{fork}",
+ "", # blank
+ "License notice: This fork retains attribution to original Calibre-Web contributors in accordance with GPL-3.0-or-later.",
+ "", # blank
+ f"Copyright (C) 2018-{year_now} Calibre-Web contributors",
+ f"Copyright (C) {START_YEAR_FORK}-{year_now} Calibre-Web Automated contributors",
+ "",
+ ]
+
+ upstream_section = format_section("Upstream Contributors", upstream, upstream_agg, include_avatars, upstream_items)
+ fork_section = format_section("Fork Contributors", fork, fork_agg, include_avatars, fork_items)
+
+ return "\n".join(header) + upstream_section + fork_section
+
+
+def main() -> int:
+ args = parse_args()
+ content = build_contributors(args.upstream, args.fork, args.include_avatars, args.timeout, args.retries)
+ with open(args.output, "w", encoding="utf-8") as f:
+ f.write(content)
+ print(f"Wrote {args.output} (contributors from {args.upstream} and {args.fork})")
+ return 0
+
+
+if __name__ == "__main__": # pragma: no cover
+ raise SystemExit(main())
diff --git a/generate_translation_status.py b/generate_translation_status.py
index 2a531b7..569bbf4 100644
--- a/generate_translation_status.py
+++ b/generate_translation_status.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
import polib
import glob
diff --git a/scripts/audiobook.py b/scripts/audiobook.py
index 141a00c..390db46 100644
--- a/scripts/audiobook.py
+++ b/scripts/audiobook.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
# This file is is mostly taken from audio.py from Ozzieisaacs at calibre-web
import base64
diff --git a/scripts/auto_library.py b/scripts/auto_library.py
index 291ce4d..508d4b6 100644
--- a/scripts/auto_library.py
+++ b/scripts/auto_library.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
import json
import os
import shutil
diff --git a/scripts/auto_zip.py b/scripts/auto_zip.py
index 00c7622..7d1b69e 100644
--- a/scripts/auto_zip.py
+++ b/scripts/auto_zip.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
import os
import sys
from os.path import isfile, join
diff --git a/scripts/convert_library.py b/scripts/convert_library.py
index 88f5c87..e94e262 100644
--- a/scripts/convert_library.py
+++ b/scripts/convert_library.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
import argparse
import json
import logging
diff --git a/scripts/cover_enforcer.py b/scripts/cover_enforcer.py
index 3d43130..1cd7e7d 100644
--- a/scripts/cover_enforcer.py
+++ b/scripts/cover_enforcer.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
import argparse
import json
import os
diff --git a/scripts/cwa_db.py b/scripts/cwa_db.py
index 3b25766..b5d1557 100644
--- a/scripts/cwa_db.py
+++ b/scripts/cwa_db.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
import sqlite3
import sys
from sqlite3 import Error as sqlError
diff --git a/scripts/ingest_processor.py b/scripts/ingest_processor.py
index 4d73b7e..65561d9 100644
--- a/scripts/ingest_processor.py
+++ b/scripts/ingest_processor.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
import atexit
import json
import os
diff --git a/scripts/kindle_epub_fixer.py b/scripts/kindle_epub_fixer.py
index 06695fe..2962070 100644
--- a/scripts/kindle_epub_fixer.py
+++ b/scripts/kindle_epub_fixer.py
@@ -1,3 +1,9 @@
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
import os
import re
import zipfile
diff --git a/update_spdx_headers.py b/update_spdx_headers.py
new file mode 100644
index 0000000..648cd3e
--- /dev/null
+++ b/update_spdx_headers.py
@@ -0,0 +1,239 @@
+#!/usr/bin/env python3
+# Calibre-Web Automated – fork of Calibre-Web
+# Copyright (C) 2018-2025 Calibre-Web contributors
+# Copyright (C) 2024-2025 Calibre-Web Automated contributors
+# SPDX-License-Identifier: GPL-3.0-or-later
+# See CONTRIBUTORS for full list of authors.
+
+"""
+update_spdx_headers.py
+
+Purpose:
+ Normalize / insert concise GPL license + attribution headers with SPDX into source files.
+ Designed for Calibre-Web Automated (fork of janeczku/calibre-web).
+
+Behavior:
+ * Scans files (default: **/*.py) excluding common vendor / build / translation dirs.
+ * Detects existing legacy Calibre-Web header blocks and replaces them with the new short form.
+ * Inserts header if none present.
+ * Respects existing shebang (kept as first line).
+ * Avoids duplicating if header already normalized.
+ * Updates year range dynamically (current year) and fork start year (2024).
+
+Resulting header (example):
+ # Calibre-Web Automated – fork of Calibre-Web
+ # Copyright (C) 2018-2025 Calibre-Web contributors
+ # Copyright (C) 2024-2025 Calibre-Web Automated contributors
+ # SPDX-License-Identifier: GPL-3.0-or-later
+ # See CONTRIBUTORS for full list of authors.
+
+CLI:
+ --apply Write changes (default dry-run).
+ --paths PATH [PATH] Limit to specific paths/files.
+ --ext .py,.sh Comma separated list of extensions (default .py)
+ --exclude PAT Extra glob-style exclude (can repeat)
+ --print Print updated content to stdout (only valid when single file & --apply not set)
+ --verbose Verbose logging
+ --quiet Only errors
+ --license-only Only add SPDX line if missing (preserve existing header text otherwise)
+ --force-year-start YEAR Override upstream start year (default 2018)
+
+Exit codes:
+ 0 success, 1 errors.
+
+NOTE: Commit the CONTRIBUTORS file separately; headers reference it.
+"""
+from __future__ import annotations
+
+import argparse
+import datetime
+import pathlib
+import re
+import sys
+from typing import Iterable, List
+
+UPSTREAM_START_DEFAULT = 2018
+FORK_START = 2024
+CURRENT_YEAR = datetime.date.today().year
+
+LEGACY_PATTERNS = [
+ re.compile(r"This file is part of the Calibre-Web", re.IGNORECASE),
+ re.compile(r"GNU General Public License", re.IGNORECASE),
+]
+
+SPDX_RE = re.compile(r"SPDX-License-Identifier:\s*GPL-3\.0-or-later")
+HEADER_ALREADY_RE = re.compile(r"Calibre-Web Automated .*?SPDX-License-Identifier: GPL-3\.0-or-later", re.DOTALL)
+
+SHEBANG_RE = re.compile(r"^#!.*\n")
+CODING_RE = re.compile(r"^#.*coding[:=].*\n", re.IGNORECASE)
+
+EXCLUDE_DIRS = {
+ ".git", "__pycache__", "build", "dist", "venv", ".venv", "env", "node_modules",
+ "translations", "locale", "docs/_build", "htmlcov"
+}
+
+DEFAULT_EXTS = [".py"]
+
+HEADER_TEMPLATE = (
+ "# Calibre-Web Automated – fork of Calibre-Web\n"
+ "# Copyright (C) {upstream_start}-{year} Calibre-Web contributors\n"
+ "# Copyright (C) {fork_start}-{year} Calibre-Web Automated contributors\n"
+ "# SPDX-License-Identifier: GPL-3.0-or-later\n"
+ "# See CONTRIBUTORS for full list of authors.\n"
+ "\n"
+)
+
+
+def parse_args():
+ p = argparse.ArgumentParser(description="Normalize SPDX headers")
+ p.add_argument("--apply", action="store_true", help="Write changes (default dry-run)")
+ p.add_argument("--paths", nargs="*", help="Optional list of files/dirs to process")
+ p.add_argument("--ext", default=",".join(DEFAULT_EXTS), help="Comma separated extensions")
+ p.add_argument("--exclude", action="append", default=[], help="Additional directory/file exclude (glob contains)")
+ p.add_argument("--print", action="store_true", help="Print updated content (only single file dry-run)")
+ p.add_argument("--verbose", action="store_true")
+ p.add_argument("--quiet", action="store_true")
+ p.add_argument("--license-only", action="store_true", help="Only inject SPDX if missing")
+ p.add_argument("--force-year-start", type=int, default=UPSTREAM_START_DEFAULT)
+ return p.parse_args()
+
+
+def log(msg: str, *, verbose=False, quiet=False):
+ if quiet:
+ return
+ if verbose:
+ print(msg)
+
+
+def collect_paths(paths: List[str] | None, exts: List[str], extra_excludes: List[str]) -> Iterable[pathlib.Path]:
+ if not paths:
+ roots = [pathlib.Path.cwd()]
+ else:
+ roots = [pathlib.Path(p).resolve() for p in paths]
+ for root in roots:
+ if root.is_file():
+ if root.suffix in exts:
+ yield root
+ continue
+ for p in root.rglob("*"):
+ if p.is_dir():
+ # skip excluded dirs
+ if p.name in EXCLUDE_DIRS:
+ continue
+ if any(x for x in extra_excludes if x and x in str(p)):
+ continue
+ continue
+ if p.suffix not in exts:
+ continue
+ if any(x for x in extra_excludes if x and x in str(p)):
+ continue
+ yield p
+
+
+def has_legacy(text: str) -> bool:
+ return any(p.search(text[:1000]) for p in LEGACY_PATTERNS)
+
+
+def already_normalized(text: str) -> bool:
+ return HEADER_ALREADY_RE.search(text[:400]) is not None
+
+
+def build_header(upstream_start: int) -> str:
+ return HEADER_TEMPLATE.format(upstream_start=upstream_start, fork_start=FORK_START, year=CURRENT_YEAR)
+
+
+def extract_preamble(text: str):
+ shebang = ""
+ coding = ""
+ rest = text
+ m = SHEBANG_RE.match(rest)
+ if m:
+ shebang = m.group(0)
+ rest = rest[len(shebang):]
+ m2 = CODING_RE.match(rest)
+ if m2:
+ coding = m2.group(0)
+ rest = rest[len(coding):]
+ return shebang, coding, rest
+
+
+LEGACY_BLOCK_RE = re.compile(
+ r"^(?:#.*Calibre-Web.*\n)(?:#.*\n){0,40}#.*http.*gnu.*licenses.*\n", re.IGNORECASE | re.MULTILINE
+)
+
+
+def normalize(text: str, upstream_start: int, license_only: bool) -> str:
+ if license_only:
+ if SPDX_RE.search(text):
+ return text # nothing
+ # append SPDX at top after any shebang/coding line
+ shebang, coding, rest = extract_preamble(text)
+ header = f"{shebang}{coding}# SPDX-License-Identifier: GPL-3.0-or-later\n"
+ return header + rest
+
+ if already_normalized(text):
+ # Maybe year change? Replace years if outdated
+ def repl_years(match: re.Match):
+ return build_header(upstream_start)
+ # Replace only first occurrence of our template block start
+ return re.sub(r"^# Calibre-Web Automated .*?\n\n", repl_years, text, count=1, flags=re.DOTALL)
+
+ shebang, coding, rest = extract_preamble(text)
+
+ # Remove legacy if present
+ new_rest = LEGACY_BLOCK_RE.sub("", rest, count=1) if has_legacy(rest) else rest
+
+ new_header = build_header(upstream_start)
+ return f"{shebang}{coding}{new_header}{new_rest.lstrip()}"
+
+
+def process_file(path: pathlib.Path, upstream_start: int, license_only: bool, apply: bool, verbose: bool, quiet: bool) -> bool:
+ try:
+ original = path.read_text(encoding="utf-8")
+ except Exception as e:
+ if not quiet:
+ print(f"ERROR: Cannot read {path}: {e}", file=sys.stderr)
+ return False
+ updated = normalize(original, upstream_start, license_only)
+ changed = updated != original
+ if changed and apply:
+ try:
+ path.write_text(updated, encoding="utf-8")
+ except Exception as e:
+ if not quiet:
+ print(f"ERROR: Cannot write {path}: {e}", file=sys.stderr)
+ return False
+ if changed:
+ log(f"Updated: {path}", verbose=verbose, quiet=quiet)
+ return True
+
+
+def main() -> int:
+ args = parse_args()
+ exts = [e if e.startswith('.') else f'.{e}' for e in args.ext.split(',') if e.strip()]
+
+ targets = list(collect_paths(args.paths, exts, args.exclude))
+ if not targets:
+ print("No matching files.")
+ return 0
+
+ ok = True
+ for p in targets:
+ if not process_file(p, args.force_year_start, args.license_only, args.apply, args.verbose, args.quiet):
+ ok = False
+
+ # Optional print (single file, dry run)
+ if args.print and not args.apply and len(targets) == 1:
+ path = targets[0]
+ text = path.read_text(encoding='utf-8')
+ print(normalize(text, args.force_year_start, args.license_only))
+
+ if not ok:
+ return 1
+ if not args.apply:
+ print("(dry-run) Use --apply to write changes.")
+ return 0
+
+
+if __name__ == "__main__": # pragma: no cover
+ raise SystemExit(main())