This commit is contained in:
2026-05-18 11:03:53 +02:00
parent 1f25bcbc14
commit 6498c4e444
4 changed files with 359 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
# RozkladScanu — poznámky
## Skript: `rozloz_brozuru.py`
Univerzální skript pro oba druhy skenování níže. Vše se odvodí automaticky z PDF.
```
python rozloz_brozuru.py vstup/<soubor.pdf> vystup/<soubor_serazena.pdf>
```
Pokud není zadán výstup, pojmenuje se `<vstup>_serazena.pdf` ve stejném adresáři jako vstup.
---
## Druh skenování: KONICA_DUPLEX_OD_STREDU
**Použití:** brožura (sešit) se sedlovým šitím, rozešitá a skenovaná oboustranně od prostřední stránky směrem ven na Konice.
**Podmínky:**
- Každý sken = dvojstránka A3 landscape (levá + pravá A4 vedle sebe)
- Skenování začíná od středového listu a postupuje ven
- Každý list: nejdřív přední strana, pak zadní
**Příklad — brožura 24 stran (střed 12+13), 12 PDF stránek:**
| PDF str. | Levá | Pravá |
|----------|:----:|:-----:|
| 1 | 12 | 13 |
| 2 | 14 | 11 |
| 3 | 10 | 15 |
| ... | ... | ... |
| 12 | 24 | 1 |
Výstup: 24 stránek A4, seřazeny 124.
---
## Druh skenování: RICOH_DUPLEX_OD_STREDU
**Použití:** brožura (sešit) se sedlovým šitím, rozešitá a skenovaná oboustranně od prostřední stránky směrem ven na RICOHu.
**Podmínky:**
- Každý sken = A4 landscape (levá + pravá A5 vedle sebe)
- Skenování začíná od středového listu a postupuje ven
- Každý list: nejdřív přední strana, pak zadní
**Příklad — brožura 80 stran (střed 40+41), 40 PDF stránek:**
| PDF str. | Levá | Pravá |
|----------|:----:|:-----:|
| 1 | 40 | 41 |
| 2 | 42 | 39 |
| 3 | 38 | 43 |
| ... | ... | ... |
| 40 | 80 | 1 |
Výstup: 80 stránek A5, seřazeny 180.
---
## Algoritmus (společný pro oba druhy)
Střed se odvodí automaticky: `CENTER_LEFT = počet PDF stránek`, `CENTER_RIGHT = počet PDF stránek + 1`.
Pro list č. `k` (0-based od středu):
- přední strana (PDF `2k+1`): levá = `CENTER_LEFT - 2k`, pravá = `CENTER_RIGHT + 2k`
- zadní strana (PDF `2k+2`): levá = `CENTER_RIGHT + 2k + 1`, pravá = `CENTER_LEFT - 2k - 1`
Každá PDF stránka se ořízne na levou a pravou polovinu (vždy stejný formát — poloviční šířka).
+9
View File
@@ -0,0 +1,9 @@
# Jak skenovat brožuru
## KONICA
Rozlož brožuru středem nahoru a textem k sobě.
## RICOH
Rozlož brožuru a dej do skeneru tak, aby zadní strana byla střed brožury a dolní okraj textu na střední stránce aby byl vlevo.
+205
View File
@@ -0,0 +1,205 @@
"""
navrh_pojmenovani.py — Claude navrhne jmeno souboru pro naskenovanou brozuru.
Vezme prvnich N stranek PDF, posle je Claudovi jako obrazky a navrhne nazev.
Dialog umozni opravit navrh. Historie dvojic (navrh Claudea, volba uzivatele)
se ulozi a priste prikladem uci Claudea pojmenovavat podle tvych preferencí.
Pouziti:
python navrh_pojmenovani.py vystup/brozura.pdf
python navrh_pojmenovani.py vystup/brozura.pdf vystup/muj_nazev.pdf
"""
import sys
import json
import base64
import io
import copy
import tkinter as tk
from pathlib import Path
from datetime import date
import fitz # pymupdf
import anthropic
HISTORY_FILE = Path(__file__).parent / "pojmenovani_historie.json"
N_PAGES = 5
DPI = 120 # dostatecne pro cteni obsahu, nizke naklady na tokeny
# ---------- pomocne funkce ----------
def load_history() -> list:
if HISTORY_FILE.exists():
return json.loads(HISTORY_FILE.read_text(encoding="utf-8"))
return []
def save_history(history: list):
HISTORY_FILE.write_text(
json.dumps(history, ensure_ascii=False, indent=2),
encoding="utf-8",
)
def pdf_pages_to_b64(pdf_path: str, n: int = N_PAGES) -> list[str]:
"""Vrati seznam base64 JPEG retezcu pro prvnich n stranek PDF."""
doc = fitz.open(pdf_path)
pages = min(n, len(doc))
result = []
mat = fitz.Matrix(DPI / 72, DPI / 72)
for i in range(pages):
pix = doc[i].get_pixmap(matrix=mat, colorspace=fitz.csRGB)
jpeg = pix.tobytes("jpeg", jpg_quality=75)
result.append(base64.standard_b64encode(jpeg).decode())
doc.close()
return result
def ask_claude(images_b64: list[str], history: list) -> str:
"""Posle stranky Claudovi a vrati navrzeny nazev souboru."""
client = anthropic.Anthropic()
history_text = ""
if history:
history_text = "\n\nPriklady z minulosti (muj navrh → uzivatel zvolil):\n"
for item in history[-15:]:
history_text += f" {item['claude']} -> {item['user']}\n"
content: list = []
for i, b64 in enumerate(images_b64):
content.append({
"type": "image",
"source": {"type": "base64", "media_type": "image/jpeg", "data": b64},
})
today = date.today().strftime("%Y-%m-%d")
content.append({
"type": "text",
"text": (
f"Toto je prvnich {len(images_b64)} stranek naskenované brozury. "
"Navrhni vhodny kratky nazev souboru (bez pripony). "
f"Zacni vzdy datem dnesniho dne ve formatu YYYY-MM-DD ({today}), "
"pak podtrzitko a popis obsahu. "
"Pouzij pouze ASCII znaky, cisla, podtrzitka nebo pomlcky — zadna diakritika ani mezery. "
"Popis ma vystihovat obsah dokumentu, byt strucny (24 slova)."
f"{history_text}\n\n"
"Odpovez pouze samotnym nazvem souboru, nic jineho. "
f"Priklad spravneho formatu: {today}_nazev_dokumentu"
),
})
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=64,
thinking={"type": "adaptive"},
messages=[{"role": "user", "content": content}],
)
for block in response.content:
if block.type == "text":
return block.text.strip().strip('"').strip("'")
return "brozura"
def dialog(claude_suggestion: str) -> dict:
"""Zobrazi dialog s navrzenim nazvem, uzivatel muze opravit."""
root = tk.Tk()
root.title("Navrh pojmenovani")
root.resizable(False, False)
root.attributes("-topmost", True)
result = {"name": None, "cancelled": False}
tk.Label(
root, text="Claude navrhuje:", font=("Segoe UI", 9, "italic"), fg="#555"
).pack(padx=24, pady=(16, 2), anchor="w")
tk.Label(
root, text=claude_suggestion, font=("Segoe UI", 12, "bold"), fg="#1a6aaa"
).pack(padx=24, anchor="w")
tk.Label(
root, text="Nazev souboru (uprav pokud chces):", font=("Segoe UI", 9)
).pack(padx=24, pady=(12, 2), anchor="w")
entry = tk.Entry(root, width=52, font=("Segoe UI", 10))
entry.insert(0, claude_suggestion)
entry.pack(padx=24)
entry.select_range(0, tk.END)
entry.focus_set()
def ok(event=None):
val = entry.get().strip()
if val:
result["name"] = val
root.destroy()
def cancel(event=None):
result["cancelled"] = True
root.destroy()
btn = tk.Frame(root)
btn.pack(pady=16)
tk.Button(btn, text="OK", width=12, command=ok).pack(side=tk.LEFT, padx=6)
tk.Button(btn, text="Zrusit", width=12, command=cancel).pack(side=tk.LEFT, padx=6)
root.bind("<Return>", ok)
root.bind("<Escape>", cancel)
root.mainloop()
return result
# ---------- hlavni logika ----------
def main():
if len(sys.argv) < 2:
print(__doc__)
sys.exit(1)
pdf_path = Path(sys.argv[1])
if not pdf_path.exists():
print(f"Soubor nenalezen: {pdf_path}")
sys.exit(1)
# vystupni cesta — volitelny druhy argument
out_path: Path | None = None
if len(sys.argv) > 2:
out_path = Path(sys.argv[2])
print(f"Nacitam stranky z: {pdf_path}")
images = pdf_pages_to_b64(str(pdf_path), N_PAGES)
print(f" {len(images)} stranek prevedeno na obrazky")
history = load_history()
print(f" {len(history)} polozek v historii pojmenovani")
print("Ptam se Claudea...")
claude_name = ask_claude(images, history)
print(f" Claude navrhuje: {claude_name}")
res = dialog(claude_name)
if res["cancelled"] or not res["name"]:
print("Zruseno — soubor nebyl prejmenovan.")
return
user_name = res["name"]
# uloz do historie
history.append({"claude": claude_name, "user": user_name})
save_history(history)
print(f" Historie ulozena ({len(history)} polozek)")
# urceni vystupniho souboru
if out_path is None:
out_path = pdf_path.parent / (user_name + pdf_path.suffix)
if out_path != pdf_path:
pdf_path.rename(out_path)
print(f"Prejmenovan: {out_path}")
else:
print(f"Nazev beze zmeny: {out_path}")
if __name__ == "__main__":
main()
+76
View File
@@ -0,0 +1,76 @@
"""
Rozlozi naskenovanou brozuru do PDF se strankami razenymi od 1.
Predpoklady:
- Kazda PDF stranka obsahuje dve stranky brozury vedle sebe (leva + prava).
- Sken probíhal duplexne od stredoveho listu smerem ven.
- Stredove stranky se odvodi automaticky z poctu PDF stranek.
Pouziti:
python rozloz_brozuru.py <vstup.pdf> [vystup.pdf]
"""
import sys
import copy
from pypdf import PdfReader, PdfWriter
from pypdf.generic import RectangleObject
def rozloz(input_path, output_path):
reader = PdfReader(input_path)
n_pdf = len(reader.pages)
center_left = n_pdf # napr. 12 nebo 40
center_right = n_pdf + 1 # napr. 13 nebo 41
total_pages = n_pdf * 2 # celkovy pocet stran brozury
print(f"PDF stranek : {n_pdf}")
print(f"Stran brozury: {total_pages} (stred: {center_left}+{center_right})")
# Mapovani: brozurova strana -> (pdf_idx 0-based, 'L'/'P')
page_map = {}
n_sheets = n_pdf // 2
for k in range(n_sheets):
front_pdf = 2 * k
back_pdf = 2 * k + 1
page_map[center_left - 2 * k] = (front_pdf, 'L')
page_map[center_right + 2 * k] = (front_pdf, 'P')
page_map[center_right + 2 * k + 1] = (back_pdf, 'L')
page_map[center_left - 2 * k - 1] = (back_pdf, 'P')
writer = PdfWriter()
for bp in range(1, total_pages + 1):
pdf_idx, lr = page_map[bp]
src = reader.pages[pdf_idx]
width = float(src.mediabox.width)
height = float(src.mediabox.height)
half_w = width / 2
new_page = copy.copy(src)
if lr == 'L':
box = RectangleObject([0, 0, half_w, height])
else:
box = RectangleObject([half_w, 0, width, height])
new_page.mediabox = box
new_page.cropbox = box
writer.add_page(new_page)
with open(output_path, "wb") as f:
writer.write(f)
print(f"Hotovo: {output_path} ({total_pages} stranek)")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Pouziti: python rozloz_brozuru.py <vstup.pdf> [vystup.pdf]")
sys.exit(1)
inp = sys.argv[1]
out = sys.argv[2] if len(sys.argv) > 2 else inp.replace(".pdf", "_serazena.pdf")
rozloz(inp, out)