Windows compatibility

This commit is contained in:
2026-08-20 12:01:31 +02:00
parent 7c366a9ca4
commit ac4ab782b2
20 changed files with 634 additions and 134 deletions
+109
View File
@@ -0,0 +1,109 @@
from __future__ import annotations
import argparse
import uuid
from pathlib import Path
from pypdf import PdfReader, PdfWriter
def copy_pdfs(directory: Path) -> list[Path]:
directory = directory.expanduser().resolve()
if not directory.is_dir():
raise NotADirectoryError(f"Dossier introuvable : {directory}")
return sorted(
(
path
for path in directory.glob("*.pdf")
if path.name.casefold() not in {"enonce.pdf", "énoncé.pdf"}
),
key=lambda path: path.name.casefold(),
)
def rotate_pdf(path: Path) -> None:
temporary = path.with_name(f".{path.stem}.rotate-{uuid.uuid4().hex}.pdf")
try:
with path.open("rb") as source, temporary.open("wb") as destination:
reader = PdfReader(source)
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page.rotate(180))
if reader.metadata:
metadata = {
str(key): str(value)
for key, value in reader.metadata.items()
if value is not None
}
writer.add_metadata(metadata)
writer.write(destination)
temporary.replace(path)
finally:
if temporary.exists():
temporary.unlink()
def rotate_all(directory: Path) -> int:
files = copy_pdfs(directory)
for path in files:
rotate_pdf(path)
print(f"Rotated: {path.name}")
if not files:
print("No PDF copies found.")
return len(files)
def rename_all(directory: Path) -> list[tuple[Path, Path]]:
directory = directory.expanduser().resolve()
files = copy_pdfs(directory)
width = max(2, len(str(len(files))))
plan = [
(source, directory / f"Copie{index:0{width}d}.pdf")
for index, source in enumerate(files, start=1)
]
staged: list[tuple[Path, Path, Path]] = []
try:
for source, destination in plan:
temporary = directory / f".copienator-rename-{uuid.uuid4().hex}.pdf"
source.replace(temporary)
staged.append((source, temporary, destination))
completed: list[tuple[Path, Path, Path]] = []
try:
for source, temporary, destination in staged:
temporary.replace(destination)
completed.append((source, temporary, destination))
print(f"Renamed: {source.name} -> {destination.name}")
except OSError:
for _source, temporary, destination in completed:
if destination.exists():
destination.replace(temporary)
raise
except OSError:
for source, temporary, _destination in staged:
if temporary.exists():
temporary.replace(source)
raise
if not plan:
print("No PDF copies found.")
return [(source, destination) for source, _temporary, destination in staged]
def main() -> None:
parser = argparse.ArgumentParser(description="Prepare scanned PDF copies.")
subparsers = parser.add_subparsers(dest="operation", required=True)
for operation in ("rotate", "rename"):
subparser = subparsers.add_parser(operation)
subparser.add_argument("directory", type=Path)
args = parser.parse_args()
if args.operation == "rotate":
rotate_all(args.directory)
else:
rename_all(args.directory)
if __name__ == "__main__":
main()