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