z230
This commit is contained in:
@@ -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 1–24.
|
||||
|
||||
---
|
||||
|
||||
## 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 1–80.
|
||||
|
||||
---
|
||||
|
||||
## 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).
|
||||
@@ -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.
|
||||
@@ -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 (2–4 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()
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user