pdf-extract

Extrakce ručních poznámek, zvýraznění, podtržení a označených obrázků z PDF knih. Detekuje barevné anotace (zelené, žluté, červené, modré, černé) a vytvoří strukturovaný .md soubor s přepisem.

PDF Extract – extrakce poznámek z knih v4.1

Kdy použít

  • Uživatel chce vytáhnout své ruční poznámky a zvýraznění z PDF knihy
  • Trigger fráze: "extrahuj poznámky", "poznámky z PDF", "/pdf-extract", "vytáhni z knihy"

Vstup

  • Cesta k PDF souboru (povinné)
  • Volitelně: výstupní cesta, konkrétní rozsah stran

Kontext uživatele

  • Uživatel anotuje digitálním perem na tabletu
  • Typické anotace: čárky u textu, ohraničení pasáží, podtržení, sem tam ruční poznámka
  • Tahy jsou vždy nepravidelné (nikdy dokonale rovné) – to je klíčový rozlišovací znak od tištěného obsahu
  • Schémata a diagramy v knize mají být zachovány jako obrázky, NE převáděny na text

Dávkový režim (výchozí chování)

KRITICKÉ – MUSÍŠ DODRŽET:

  • Výchozí: zpracuj prvních 50 stran, vytvoř výstupní .md soubor a ZASTAV SE
  • STOP po prvních 50 stranách – vypiš uživateli výsledky a ČEKEJ na jeho odpověď
  • Uživatel si výstup zkontroluje a řekne, jestli pokračovat s dalšími stranami
  • NEPOKRAČUJ automaticky na strany 51+ bez explicitního pokynu uživatele
  • Pokud uživatel explicitně řekne "zpracuj celou knihu" / "všechny strany" / "pokračuj", teprve pak pokračuj s dalšími stranami
  • Při pokračování přidávej do existujícího .md souboru, nemazej předchozí výsledky

Architektura – šetření kontextu

KRITICKÉ: Tento skill zpracovává desítky až stovky stran s obrázky. Aby nedocházelo k vyčerpání kontextového okna:

  1. Fáze 1 (detekce) běží v hlavním kontextu – je levná, výstupem je jen seznam čísel stran
  2. Fáze 2 (vizuální analýza + extrakce) se deleguje na Agent subagenty s model: "opus" (1M kontext)
  3. Subagenty zpracovávají dávky stran a vrací pouze strukturovaný markdown text, ne obrázky
  4. Hlavní kontext pak jen skládá výsledky z subagentů do výstupního souboru

Kategorie stran podle náročnosti zpracování:

  • Jednoduché (PDF annotations: highlight, underline, strikeout) → extrahuj přímo z PDF dat, BEZ vizuální kontroly
  • Marginální (vektorové kresby POUZE na okrajích stránky, tj. x < 60 nebo x > page_width - 60) → programatická extrakce spárováním souřadnic kreseb s textovými bloky (krok 4A-2), BEZ vizuální kontroly – toto je spolehlivější než vizuální analýza, protože fill-only paths se v PNG renderují jako extrémně tenké/neviditelné čáry
  • Složité (vektorové kresby v těle stránky, ink anotace, barevné pixely, ruční text) → vyžadují vizuální kontrolu přes PNG → deleguj na subagenty

Workflow

1. Instalace závislostí (pokud chybí)

Ověř dostupnost a nainstaluj pokud chybí:

pip3 install PyMuPDF Pillow numpy

2. Zjisti metadata knihy

import fitz  # PyMuPDF
doc = fitz.open("<cesta_k_pdf>")
title = doc.metadata.get("title", "")
author = doc.metadata.get("author", "")
page_count = len(doc)
  • Pokud metadata chybí, odvoď název z názvu souboru
  • Informuj uživatele: název, autor, počet stran

3. Detekce anotovaných stran (Fáze 1 – hlavní kontext)

Zpracovávej po dávkách (50–100 stran), aby se šetřila paměť.

3a. Detekce barevných pixelových anotací

Renderuj stránky přes PyMuPDF a hledej barevné pixely:

import numpy as np
from PIL import Image

pix = page.get_pixmap(dpi=100)
img = np.frombuffer(pix.samples, dtype=np.uint8).reshape(pix.h, pix.w, pix.n)
r, g, b = img[:,:,0], img[:,:,1], img[:,:,2]

Detekční pravidla pro jednotlivé barvy:

BarvaPodmínkaVýznam
Černáviz krok 3b (vektorová detekce)Hlavní barva – poznámky, podtržení, zatržení
Zelená(g > 100) & (g > r*1.2) & (g > b*1.2) & ((g-r) > 30)Zvýraznění, zatržení
Žlutá(r > 150) & (g > 150) & (b < 100) & (r+g > 350)Highlighter
Červená(r > 150) & (r > g*1.5) & (r > b*1.5)Důležité, opravy
Modrá(b > 100) & (b > r*1.2) & (b > g*1.2) & ((b-r) > 30)Poznámky, otázky
Oranžová(r > 180) & (g > 80) & (g < 160) & (b < 80)Doplňkové zvýraznění

Práh: > 50 barevných pixelů dané barvy = podezřelá strana

3b. Detekce černých anotací (PRIMÁRNÍ – uživatel píše digitálním perem)

Černé anotace jsou nejčastější typ. Detekce je náročnější, protože tištěný text je také černý.

KLÍČOVÉ ROZLIŠENÍ ruční vs. tištěné: Uživatel píše digitálním perem na tabletu. Jeho tahy jsou vždy nepravidelné – nikdy dokonale rovné čáry, body v nepravidelných rozestupech, mírné odchylky od přímky. Tištěné prvky mají přesnou geometrii – dokonale rovné čáry, přesné úhly, pravidelné rozestupy.

Přístup 1: Vektorové kresby (drawings)

drawings = page.get_drawings()

DŮLEŽITÉ – kontroluj OBOJÍ: color (stroke) i fill (výplň): Některé PDF ukládají ruční anotace jako filled paths (výplň) místo stroked paths (obrys). Mnoho digitálních per generuje color=None, fill=(0,0,0).

import math

def is_hand_drawn(items):
    """Rozliš ruční tah od tištěného prvku.

    Ruční tahy (digitální pero na tabletu):
    - Mnoho bodů v krátkém úseku (pero sampluje polohu)
    - Body NEJSOU na dokonalé přímce – odchylka od spojnice > 0
    - Křivky (bezier curves) = téměř jistě ruční
    - Celkový tvar je nepravidelný

    Tištěné prvky (layout PDF):
    - Málo bodů (typicky 2-4 pro čáry a obdélníky)
    - Dokonale rovné čáry (odchylka = 0)
    - Pravidelné obdélníky, kruhy
    - Přesné horizontální/vertikální zarovnání
    """
    has_curves = False
    line_segments = []
    point_count = 0

    for item in items:
        op = item[0]  # typ operace: "l" (line), "c" (curve), "re" (rect), "qu" (quad)

        if op == "c":  # bezier křivka = téměř jistě ruční
            has_curves = True
            point_count += 4
        elif op == "l":  # úsečka
            p1, p2 = item[1], item[2]
            dx = abs(p2.x - p1.x)
            dy = abs(p2.y - p1.y)
            length = math.sqrt(dx*dx + dy*dy)
            line_segments.append((p1, p2, length, dx, dy))
            point_count += 2
        elif op == "re":  # obdélník = typicky tištěný layout
            point_count += 4
        elif op == "qu":  # quad = typicky tištěný
            point_count += 4

    # Bezier křivky = ruční
    if has_curves:
        return True

    # Samé obdélníky/quady bez čar = pravděpodobně layout
    if not line_segments and not has_curves:
        return False

    # Mnoho krátkých úseček za sebou = samplovaný tah pera
    if len(line_segments) > 5:
        short_segments = sum(1 for _, _, l, _, _ in line_segments if l < 15)
        if short_segments > len(line_segments) * 0.5:
            return True

    # Kontrola nepravidelnosti: pokud úsečky NEJSOU dokonale horizontální/vertikální
    irregular_count = 0
    for _, _, length, dx, dy in line_segments:
        if length < 1:
            continue
        # Dokonale rovná čára: dx==0 nebo dy==0 (nebo téměř)
        angle_from_axis = min(dx, dy) / max(dx, dy) if max(dx, dy) > 0 else 0
        if angle_from_axis > 0.02:  # > 1° odchylka od osy = nepravidelné
            irregular_count += 1

    if irregular_count > 0 and irregular_count >= len(line_segments) * 0.3:
        return True

    return False

for d in drawings:
    color = d.get("color", None)
    fill = d.get("fill", None)
    items = d.get("items", [])

    is_black_stroke = (color is not None and len(color) >= 3
                       and color[0] < 0.2 and color[1] < 0.2 and color[2] < 0.2)
    is_black_fill = (fill is not None and len(fill) >= 3
                     and fill[0] < 0.2 and fill[1] < 0.2 and fill[2] < 0.2)
    is_bg_rect = (fill is not None and len(fill) >= 3
                  and fill[0] > 0.9 and fill[1] > 0.9 and fill[2] > 0.9)

    if is_bg_rect:
        continue  # šedé/bílé pozadí citátů – ignorovat

    if (is_black_stroke or is_black_fill) and len(items) > 1:
        # KLÍČOVÉ: ověř, že jde o ruční tah, ne tištěný prvek
        if is_hand_drawn(items):
            annotation_items += len(items)

Filtrování false positives – pravidla:

  • VŽDY použij is_hand_drawn() – nepoužívej jen počet items jako práh
  • Obdélníky (re) = téměř vždy layout (rámečky, boxy) → ignorovat, pokud nejsou nepravidelné
  • Dokonale rovné čáry (horizontální/vertikální, odchylka < 1°) = tištěný prvek → ignorovat
  • Bezier křivky = téměř jistě ruční → vždy zahrnout
  • Mnoho krátkých úseček v řadě = samplovaný tah digitálního pera → zahrnout
  • Nepravidelné úhly (odchylka > 1° od osy) = ruční tah → zahrnout
  • Práh pro celou stranu: součet items z potvrzeně ručních kreseb > 5 = anotovaná strana

Přístup 2: Ink anotace (PDF annotation layer)

for annot in page.annots():
    if annot.type[0] in [8, 15]:  # Ink, Polyline
        # Ink anotace = vždy ruční → zahrnout
        pass

Dodatečné filtry pro snížení false positives:

  • Ignoruj kresby uvnitř tabulek – tabulkové čáry jsou tištěné
  • Ignoruj dekorativní prvky u nadpisů/zápatí (opakující se vzor na více stranách)
  • Pokud se stejný drawing pattern opakuje na > 30 % stran, je to pravděpodobně layout → vyloučit
  • Buď přesný – cíl je zachytit VŠECHNY ruční anotace a ŽÁDNÉ tištěné prvky; při pochybnosti pošli stranu na vizuální kontrolu subagentovi, který rozhodne

3c. Detekce vestavěných PDF anotací

annots = page.annots()
  • Zkontroluj vestavěné PDF anotace (highlight, underline, strikeout, text notes, ink)
  • Typy: Highlight, Underline, StrikeOut, Text, FreeText, Ink, Square, Circle
  • Extrahuj barvu a obsah anotace

3d. Klasifikace pozice kreseb na stránce

Pro každou stranu s detekovanými vektorovými kresbami analyzuj POZICI kreseb:

def classify_drawing_position(drawing, page_width):
    """Určí, zda je kresba na okraji stránky nebo v těle textu."""
    items = drawing.get("items", [])
    xs, ys = [], []
    for item in items:
        for pt in item[1:]:
            if hasattr(pt, 'x'):
                xs.append(pt.x)
                ys.append(pt.y)
    if not xs:
        return None, None, None, None

    min_x, max_x = min(xs), max(xs)
    min_y, max_y = min(ys), max(ys)
    x_span = max_x - min_x
    y_span = max_y - min_y

    # Svislá čára na okraji = zatržení
    if x_span < 30:  # úzká kresba = svislá čára
        if min_x < 60:
            return "left_margin", min_x, min_y, max_y
        elif min_x > page_width - 60:
            return "right_margin", min_x, min_y, max_y

    # Široká kresba na okraji
    if max_x < 60:
        return "left_margin", min_x, min_y, max_y
    if min_x > page_width - 60:
        return "right_margin", min_x, min_y, max_y

    return "body", min_x, min_y, max_y

Pro každou stranu s potvrzeně ručními kresbami ulož:

  • Seznam kreseb s jejich pozicí (left_margin / right_margin / body)
  • Souřadnice y-rozsahu každé kresby
  • Počet items v kresbě

3e. Kategorizace nalezených stran

Rozděl nalezené strany do tří skupin:

Jednoduché strany (BEZ vizuální kontroly):

  • Mají POUZE vestavěné PDF anotace typu Highlight, Underline, StrikeOut
  • Text pod anotací lze extrahovat přímo z PDF dat přes page.get_text()
  • Zpracuj tyto strany přímo v hlavním kontextu (viz krok 4A-1)

Marginální strany (programatická extrakce, BEZ vizuální kontroly):

  • Mají vektorové kresby POUZE na okrajích stránky (left_margin / right_margin)
  • Žádné kresby v těle stránky (body)
  • Text se extrahuje programaticky spárováním y-souřadnic kreseb s textovými bloky (viz krok 4A-2)
  • DŮLEŽITÉ: Toto je spolehlivější než vizuální analýza! Fill-only paths (color=None, fill=(0,0,0)) se v PNG renderují jako extrémně tenké/neviditelné čáry a subagent je nevidí

Složité strany (VYŽADUJÍ vizuální kontrolu):

  • Mají vektorové kresby v těle stránky, ink anotace, barevné pixelové anotace, nebo ruční text
  • Obsahují ruční poznámky, šipky, symboly, schémata
  • Deleguj na subagenty (viz krok 4B)

Vypiš uživateli přehled: kolik stran nalezeno, kolik jednoduchých vs. marginálních vs. složitých.

4A-1. Přímá extrakce jednoduchých stran (hlavní kontext)

Pro strany s POUZE vestavěnými PDF anotacemi (highlight, underline, strikeout):

for annot in page.annots():
    annot_type = annot.type[1]  # "Highlight", "Underline", etc.
    annot_color = annot.colors.get("stroke", None) or annot.colors.get("fill", None)
    # Získej text pod anotací
    text = page.get_text("text", clip=annot.rect)
  • Extrahuj text přímo z PDF – žádné PNG, žádná vizuální analýza
  • Zapiš rovnou do výstupního markdown (buffer v paměti)
  • Toto je extrémně levné na kontext

4A-2. Programatická extrakce marginálních stran (hlavní kontext)

Pro strany s vektorovými kresbami POUZE na okrajích stránky. Toto je spolehlivější než vizuální analýza pro zatržení/podtržení, protože fill-only paths se v PNG renderují jako neviditelné.

def get_text_in_y_range(page, y_min, y_max, margin=15):
    """Získej textové řádky v daném rozsahu y-souřadnic.

    DŮLEŽITÉ: margin=15 (ne 5!) – kresby často začínají/končí
    mírně mimo textový blok, větší margin zajistí správné párování.
    """
    blocks = page.get_text("dict")["blocks"]
    lines = []
    for b in blocks:
        if "lines" not in b:
            continue
        for line in b["lines"]:
            ly = (line["bbox"][1] + line["bbox"][3]) / 2  # střed řádku
            if y_min - margin <= ly <= y_max + margin:
                text = " ".join(span["text"] for span in line["spans"])
                if text.strip():
                    lines.append(text.strip())
    return "\n".join(lines)

def merge_ranges(ranges, gap=10):
    """Slučuj překrývající se y-rozsahy.

    KRITICKÉ: Řaď podle y_min (x[1]), NE podle position stringu (x[0])!
    Špatné řazení způsobí nesprávné sloučení nepřekrývajících se rozsahů
    a ztrátu anotací.
    """
    if not ranges: return []
    ranges.sort(key=lambda x: x[1])  # řaď podle y_min, NE podle position!
    merged = [list(ranges[0])]
    for pos, y_min, y_max in ranges[1:]:
        if y_min <= merged[-1][2] + gap:
            merged[-1][2] = max(merged[-1][2], y_max)
        else:
            merged.append([pos, y_min, y_max])
    return merged

# Pro každou marginální stranu:
page = doc[page_num - 1]
pw = page.rect.width

# Seskup kresby podle pozice a y-rozsahu
margin_annotations = []  # [(position, y_min, y_max)]
for d in drawings:
    # ... filtruj jen potvrzeně ruční černé kresby ...
    pos, min_x, min_y, max_y = classify_drawing_position(d, pw)
    if pos in ("left_margin", "right_margin") and (max_y - min_y) > 20:
        margin_annotations.append((pos, min_y, max_y))

# Slučuj překrývající se rozsahy (POZOR: merge_ranges musí řadit dle y_min!)
merged = merge_ranges(margin_annotations)
# Pak pro každý rozsah:
for pos, y_min, y_max in merged:
    text = get_text_in_y_range(page, y_min, y_max)
    # Zapiš jako [ZATRŽENO] s poznámkou o pozici (left/right/oboustranné)

Pravidla pro marginální extrakci:

  • Kresby s y_span < 20pt ignoruj (příliš malé = pravděpodobně tečky/artefakty)
  • Pokud je kresba na OBOU okrajích (left + right) ve stejném y-rozsahu → "Oboustranné zatržení"
  • Pokud je kresba pouze na jednom okraji → "[ZATRŽENO]" bez speciální poznámky
  • Text extrahuj přes get_text("dict") pro přesné řádky, NE přes get_text("text", clip=...) – clip je méně přesný
  • Toto je extrémně levné na kontext a spolehlivější než vizuální analýza pro okrajové anotace

4B. Vizuální analýza složitých stran (delegovaná na subagenty)

KRITICKÉ pro šetření kontextu: Vizuální analýzu NIKDY neděle v hlavním kontextu. Vždy deleguj na Agent subagenty.

4B-1. Příprava PNG souborů

Před spuštěním subagentů exportuj všechny složité strany jako PNG:

pix = page.get_pixmap(dpi=250)  # 250 DPI – vyšší rozlišení pro spolehlivou detekci jemných tahů
pix.save(f"temp_pages/page_{page_num:04d}.png")

Také pro každou stranu extrahuj tištěný text a drawing metadata:

text = page.get_text()
with open(f"temp_pages/page_{page_num:04d}.txt", "w") as f:
    f.write(text)

# Drawing metadata – pomůže subagentovi vědět KAM se dívat
with open(f"temp_pages/page_{page_num:04d}_hints.txt", "w") as f:
    for d in page.get_drawings():
        fill = d.get("fill", None)
        color = d.get("color", None)
        items = d.get("items", [])
        is_black = (fill and fill[0] < 0.2) or (color and color[0] < 0.2)
        if is_black and len(items) > 3:
            pos, min_x, min_y, max_y = classify_drawing_position(d, page.rect.width)
            if pos:
                f.write(f"Drawing: {len(items)} items, pos={pos}, y=[{min_y:.0f}-{max_y:.0f}]\n")

4B-2. Spuštění subagentů po dávkách

Rozděl složité strany do dávek po max 15 stranách. Pro každou dávku spusť Agent subagenta:

Agent(
    model: "opus",    # 1M kontext – unese 15 stran s obrázky
    description: "PDF extract batch X",
    prompt: """
    Jsi expert na extrakci ručních poznámek z PDF knih.

    Zpracuj tyto strany: [seznam čísel stran]
    Cesta k PNG: temp_pages/page_XXXX.png
    Cesta k textu: temp_pages/page_XXXX.txt

    Pro každou stranu:
    1. Přečti soubor _hints.txt – obsahuje pozice detekovaných kreseb (kde hledat anotace)
    2. Přečti PNG přes Read tool (vizuální kontrola)
    3. Přečti textový soubor pro kontext tištěného textu
    4. Identifikuj typy anotací (viz tabulka níže) – ZAMĚŘ SE na pozice z hints souboru
    5. Pokud je na stránce označený obrázek/schéma, extrahuj ho

    Typy anotací:
    | Tag | Popis |
    |-----|-------|
    | [RUKOU PSÁNO] | Ruční text – poznámky, nadpisy, komentáře |
    | [ZATRŽENO] | Svislá čára na okraji u tištěné pasáže |
    | [ZVÝRAZNĚNO] | Highlight / rámeček kolem textu |
    | [PODTRŽENO] | Čára pod tištěným textem |
    | [OBRÁZEK] | Označený/orámovaný obrázek na stránce |
    | [SCHÉMA] | Diagram, schéma, graf – VŽDY zachovat jako obrázek, NIKDY nepřevádět na text |
    | [ŠIPKA] | Šipka ukazující na konkrétní místo |
    | [SYMBOL] | Hvězdička, vykřičník, otazník, check |
    | [ZÁLOŽKA] | Označení celé strany |

    DŮLEŽITÉ – rozlišení ručních anotací vs. tištěný obsah:
    - Uživatel anotuje digitálním perem na tabletu
    - Jeho tahy jsou VŽDY nepravidelné (nikdy dokonale rovné)
    - Tištěný text a layout prvky (čáry tabulek, rámečky, dekorativní prvky) jsou dokonale geometrické
    - NEOZNAČUJ tištěné prvky jako anotace!
    - Hledej: čárky u textu, ohraničení pasáží, podtržení, ruční poznámky na okrajích
    - Pokud si nejsi jistý, jestli je prvek ruční nebo tištěný → ZAHRŇ ho do výstupu s poznámkou [nejisté]

    SCHÉMATA A DIAGRAMY:
    - Pokud strana obsahuje schéma/diagram/graf (tištěný i ručně kreslený), VŽDY ho zachovej jako obrázek
    - NIKDY nepřeváděj schéma na textový popis
    - Extrahuj oblast schématu jako samostatný PNG pomocí PyMuPDF s clip parametrem:
      ```python
      import fitz
      doc = fitz.open("<cesta_k_pdf>")
      page = doc[page_num - 1]
      clip = fitz.Rect(x0, y0, x1, y1)  # oblast schématu z hints/vizuální analýzy
      pix = page.get_pixmap(dpi=300, clip=clip)
      pix.save(f"{nazev_knihy}_images/img_page{page_num}_{N}.png")
      ```
    - Vlož do markdown jako obrázek: `![Popis](cesta/img_pageXX_N.png)`

    VÝSTUP: Vrať POUZE strukturovaný markdown pro každou stranu v tomto formátu:

    ## Strana XX

    ### [TYP_ANOTACE] emoji_barvy
    > text anotace

    ---

    Pokud text nelze přečíst, napiš [nečitelné].
    Nevymýšlej text – pokud ho nevidíš, nevymýšlej.
    U zatržených pasáží cituj dostatečně velký blok tištěného textu.
    U ručních poznámek přepsat text kurzívou.
    Schémata a diagramy VŽDY jako obrázek, NIKDY jako text.

    Obrázky ukládej jako: {nazev_knihy}_images/img_page{XX}_{N}.png
    """
)

Paralelizace: Pokud je stran hodně (30+), spusť 2–3 subagenty paralelně (nezávislé dávky).

4B-3. Extrakce označených obrázků v subagentovi

Subagent v rámci své dávky extrahuje označené obrázky:

images = page.get_images(full=True)
for img_info in images:
    xref = img_info[0]
    base_image = doc.extract_image(xref)
    # Ulož obrázek do výstupní složky
  • Ulož obrázek jako img_page{XX}_{N}.png do složky {nazev_knihy}_images/
  • V markdown odkazuj relativní cestou

4C. Programatická verifikace po subagentovi (hlavní kontext)

KRITICKÉ: Po dokončení subagentů porovnej jejich výsledky s detekčními daty z Fáze 1.

Pro každou stranu, kterou subagent označil jako "false positive" nebo "žádné anotace":

  1. Zkontroluj, zda Fáze 1 detekovala vektorové kresby s > 10 items na okrajích (left_margin / right_margin)
  2. Pokud ANO → subagent se mýlil (fill-only paths jsou v PNG neviditelné)
  3. Použij programatickou extrakci (stejný postup jako 4A-2) k získání zatržených textů
  4. Doplň tyto strany do výstupu

Důvod: Některé PDF ukládají ruční tahy jako color=None, fill=(0,0,0) (filled paths bez stroke). Při renderování do PNG se tyto kresby zobrazí jako extrémně tenké čáry (sub-pixel), které LLM v obrázku nevidí. Vektorová data z PDF jsou v tomto případě autoritativní.

5. Sestavení výstupního souboru (hlavní kontext)

Po dokončení všech subagentů sestav výstupní soubor z:

  • Přímé extrakce jednoduchých stran (krok 4A)
  • Markdown výstupů ze subagentů (krok 4B)

5a. Název souboru

  • Formát: Nazev_knihy (Autor).md
  • Příklad: Thinking_Fast_and_Slow (Daniel Kahneman).md
  • Mezery v názvu knihy nahraď podtržítky
  • Ulož do stejné složky jako zdrojové PDF

5b. Struktura markdown souboru

# Název knihy
**Autor:** Jméno Autora
**Extrahováno:** YYYY-MM-DD
**Zdroj:** název_souboru.pdf
**Počet anotovaných stran:** XX z YY

---

<!-- Uživatelské poznámky – prostor pro vlastní text nad extrakcí -->

---

## Strana 42

### [ZATRŽENO]
> Tištěný text pasáže, která byla zatržena svislou čárou na okraji.
> Může být víceřádková.

### [RUKOU PSÁNO]
> Přepis ručně psané poznámky kurzívou

### [ZVÝRAZNĚNO] 🟡
> Text, který byl zvýrazněn žlutým highlighterem

### [PODTRŽENO]
> Podtržená věta nebo fráze

### [OBRÁZEK]
![Popis obrázku](Nazev_knihy_images/img_page42_1.png)
> Poznámka k obrázku (pokud existuje)

---

## Strana 57
...

5c. Pravidla zápisu

  • Řadit chronologicky podle čísla strany (sluč výsledky z jednoduchých stran a subagentů)
  • U barevných anotací přidat emoji indikátor barvy: ⚫ černá, 🟢 zelená, 🟡 žlutá, 🔴 červená, 🔵 modrá, 🟠 oranžová
  • Černé anotace (nejčastější) nemusí mít emoji – jsou výchozí; emoji přidávej jen u barevných
  • U zatržených/zvýrazněných pasáží citovat relevantní tištěný text v bloku >
  • U ručních poznámek přepsat text kurzívou v bloku >
  • NEMAZAT existující obsah – pokud soubor již existuje, uživatel může mít vlastní poznámky na začátku
  • Pokud soubor existuje, aktualizuj/doplň pouze sekce za oddělovačem ---

6. Úklid

rm -rf temp_pages/
  • Smaž dočasný adresář s PNG exporty a textovými soubory
  • Ponech složku s extrahovanými obrázky ({nazev_knihy}_images/)

7. Shrnutí

Vypiš uživateli:

  • Cestu k výstupnímu .md souboru
  • Cestu ke složce s obrázky (pokud existuje)
  • Počet zpracovaných stran / nalezených anotací
  • Rozložení typů anotací (kolik zatržení, kolik poznámek, atd.)
  • Stručný přehled nejčastěji anotovaných témat/kapitol

Pravidla kvality

Vždy:

  • Ověř, že PDF existuje a je čitelný
  • Zpracovávej po dávkách – nenahrávej celé PDF do paměti najednou
  • U ručních poznámek se snaž o co nejpřesnější přepis
  • Pokud text nelze přečíst, napiš [nečitelné]
  • Zachovej kontext – u zatržených pasáží cituj dostatečně velký blok textu
  • Informuj uživatele o průběhu (kolik stran zpracováno)
  • Schémata/diagramy vždy zachovej jako obrázek (PNG), nepřeváděj na text
  • Buď přesný – zachyť všechny ruční anotace, ale neoznačuj tištěné prvky; při pochybnosti pošli na vizuální kontrolu subagentovi

Nikdy:

  • Nevynechávej nalezené anotace
  • Nevymýšlej text – pokud ho nevidíš, nevymýšlej
  • Nemazej existující obsah v cílovém souboru
  • Neignoruj obrázky – pokud jsou označené, extrahuj je
  • NIKDY nečti PNG obrázky stran v hlavním kontextu – vždy deleguj na subagenty
  • NIKDY neoznačuj tištěné prvky (layout, tabulky, dekorace) jako anotace
  • NIKDY nepřeváděj schémata/diagramy na textový popis – vždy jako obrázek

Tipy

  • U knih s mnoha anotacemi (100+ stran) může zpracování trvat déle – informuj uživatele
  • Některé PDF mají anotace jako vestavěné PDF annotations (ne jen barevné pixely) – kontroluj obojí
  • Pokud je kvalita skenování nízká, zvyš DPI na 200 pro lepší detekci v subagentovi
  • Pro knihy s převážně žlutým highlighterem uprav práh detekce
  • Subagenty s model: "opus" mají 1M kontext – klidně jim dej 15 stran najednou
  • Pokud má kniha < 10 složitých stran, stačí jeden subagent; pro 30+ stran spusť 2–3 paralelně