OUTPUT #687
PARÇA 2 / 2
TOPLAM: 61543 karakter | 1283 satır
BU PARÇA: 21543 karakter
oding="utf-8")
840-        tmp.replace(state_path)
--
850-    turn_id: str = "",
851-    tool_call_id: str = "",
852-    status: str = "",
853-    **_: Any,
854-) -> None:
855:    if tool_name != "vision_analyze":
856-        return
857-
858-    active, _, _ = _production_context(session_id)
859-    if not active:
860-        return
--
862-    image_url = str(args.get("image_url") or "")
863-    question = str(args.get("question") or "")
864-    if not image_url:
865-        return
866-
867:    # A provider failure is not reviewed evidence. Mark only completed Vision
868:    # calls as seen so the provider's explicit retry can reuse the exact sheet
869-    # and question instead of forcing a meaningless evidence mutation.
870-    raw_identity = image_url[7:] if image_url.startswith("file://") else image_url
871-    try:
872-        image_identity = _sha_file(Path(raw_identity).expanduser().resolve())
873-    except Exception:
--
881-    if status and status != "ok":
882-        return
883-    if not outer or outer.get("success") is not True:
884-        return
885-    with _LOCK:
886:        _VISION_SEEN.add(signature)
887-
888-    raw_path = image_url[7:] if image_url.startswith("file://") else image_url
889-    try:
890-        image_path = Path(raw_path).expanduser().resolve()
891-    except Exception:
892-        return
893-
894:    contract_question = _expanded_vision_question_for_receipt(image_path, question)
895:    _update_repair_state_from_vision(
896-        image_path, contract_question, result, session_id, turn_id, tool_call_id
897-    )
898-
899:    if image_path.name != "proof_contact_sheet.jpg":
900-        return
901-
902:    pending_path = image_path.parent / "proof_pending.json"
903-    if not pending_path.exists():
904-        return
905-
906-    try:
907-        pending = json.loads(pending_path.read_text(encoding="utf-8"))
908-    except Exception:
909-        return
910-
911:    token = str(pending.get("proof_token") or "")
912:    if not token or f"HERMES_PROOF_GATE::{token}" not in contract_question:
913-        return
914-
915-    try:
916-        expected_sheet = Path(str(pending["contact_sheet"])).resolve()
917-        if expected_sheet != image_path:
--
927-
928-    verdict = _json_object(outer.get("analysis"))
929-    if not verdict:
930-        return
931-
932:    accepted = _proof_verdict_accepted(verdict, token)
933-    if not accepted:
934-        if verdict.get("verdict") == "FAIL":
935:            state = _read_proof_attempt_state(image_path, pending)
936-            fingerprint = pending.get("fingerprint")
937-            if state.get("last_fingerprint") != fingerprint:
938-                state["failed_attempts"] = 0
939-            state["failed_attempts"] = int(state.get("failed_attempts") or 0) + 1
940-            state["last_verdict"] = "FAIL"
941-            state["last_fingerprint"] = fingerprint
942-            state["updated_at"] = time.time()
943:            _write_proof_attempt_state(image_path, state, pending)
944-        return
945-
946:    state = _read_proof_attempt_state(image_path, pending)
947-    state["accepted"] = True
948-    state["last_verdict"] = "PASS"
949-    state["last_fingerprint"] = pending.get("fingerprint")
950-    state["updated_at"] = time.time()
951:    _write_proof_attempt_state(image_path, state, pending)
952-
953-    try:
954-        receipt = Path(str(pending["receipt"])).expanduser().resolve()
955-        receipt.parent.mkdir(parents=True, exist_ok=True)
956-        obj = {
957-            "pass": True,
958-            "receipt_source": "shorts-production-guard/post_tool_call",
959:            "verifier": "vision_analyze",
960-            "fingerprint": pending["fingerprint"],
961-            "contact_sheet": str(image_path),
962-            "contact_sheet_sha256": pending["contact_sheet_sha256"],
963-            "verdict": verdict,
964:            "vision_route": outer.get("vision_route") if isinstance(outer.get("vision_route"), dict) else None,
965-            "session_id": session_id,
966-            "turn_id": turn_id,
967-            "tool_call_id": tool_call_id,
968-            "accepted_at": time.time(),
969-        }
--
1022-    sid = str(session_id or "")
1023-    if not sid:
1024-        return None
1025-    try:
1026-        con = sqlite3.connect(str(_DB), timeout=1)
1027:        row = con.execute("SELECT source FROM sessions WHERE id=? LIMIT 1", (sid,)).fetchone()
1028-        con.close()
1029-        if row and str(row[0] or "") in {"subagent", "tool"}:
1030-            return None
1031-    except Exception:
1032-        pass
--
1036-    compact = json.dumps(contract, ensure_ascii=False, separators=(",", ":"))
1037-    return {
1038-        "context": (
1039-            "HERMES_CREATIVE_CONTRACT_ACTIVE: This Astra-approved creative direction is the "
1040-            "current production contract. Preserve its visual intent while choosing concrete "
1041:            "assets and executing the canonical pipeline. Do not silently replace it. "
1042-            f"CONTRACT={compact}"
1043-        )
1044-    }
1045-
1046-

===== /home/hermes/.hermes/skills/media/shorts-core/scripts/production_guardrails.py =====
15-FFMPEG = "/usr/bin/ffmpeg"
16-FFPROBE = "/usr/bin/ffprobe"
17-TARGET_W = 1080
18-TARGET_H = 1920
19-TARGET_FPS = 30
20:DEFAULT_CAPTION_ZONE = [90, 1380, 990, 1740]
21-
22-def run(cmd, check=True):
23-    p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
24-    if check and p.returncode:
25-        raise RuntimeError(
--
102-    if not isinstance(shots, list) or not shots:
103-        return {
104-            "pass": False,
105-            "hard_failures": ["manifest.shots missing or empty"],
106-            "warnings": [],
107:            "assets": []
108-        }
109-
110-    hard = []
111-    warnings = []
112:    assets = []
113-    sources = []
114-
115-    for i, shot in enumerate(shots):
116-        if not isinstance(shot, dict):
117-            hard.append(f"shot[{i}] is not an object")
--
174-            stats = signalstats(p, at)
175-            if "YMIN" in stats and "YMAX" in stats:
176-                yrange = stats["YMAX"] - stats["YMIN"]
177-                if yrange < 22:
178-                    warnings.append(
179:                        f"shot[{i}] extremely low luma contrast / washed-flat candidate "
180-                        f"(range={yrange:.1f}); require visual review"
181-                    )
182-            if stats.get("SATAVG", 999) < 4:
183-                warnings.append(
184-                    f"shot[{i}] extremely low average saturation; verify this is intentional"
185-                )
186-        except Exception as e:
187-            warnings.append(f"shot[{i}] signalstats unavailable: {e}")
188-
189:        assets.append({
190-            "shot": i,
191-            "src": str(p),
192-            "width": w,
193-            "height": h,
194-            "source_duration": src_dur,
--
204-
205-    for i in range(1, len(sources)):
206-        if sources[i] == sources[i-1]:
207-            warnings.append(f"adjacent shots reuse same source at indices {i-1}/{i}: {sources[i]}")
208-
209:    caption_zone = rect(data.get("caption_zone")) or DEFAULT_CAPTION_ZONE
210-
211-    graphics = collect_graphics(data)
212-    for i,shot in enumerate(shots):
213-        if isinstance(shot, dict):
214-            graphics.extend(collect_graphics(shot, f"shot[{i}]"))
215-
216-    for name,item in graphics:
217:        if item.get("ignore_caption_collision") is True:
218-            continue
219-        box = rect(item.get("bbox") or item.get("rect"))
220-        if box is None:
221-            hard.append(
222:                f"{name} has no deterministic bbox; programmatic graphics must declare bbox"
223-            )
224-            continue
225:        if intersects(box, caption_zone):
226-            hard.append(
227:                f"{name} bbox {box} overlaps caption zone {caption_zone}"
228-            )
229-
230-    return {
231-        "pass": not hard,
232-        "version": VERSION,
233:        "caption_zone": caption_zone,
234-        "hard_failures": hard,
235-        "warnings": warnings,
236:        "assets": assets,
237-    }
238-
239-def file_identity(p):
240-    p = Path(p)
241-    st = p.stat()
--
250-    with open(path, "rb") as f:
251-        for chunk in iter(lambda: f.read(1024*1024), b""):
252-            h.update(chunk)
253-    return h.hexdigest()
254-
255:def ffmpeg_escape_ass(path):
256-    s = str(Path(path).resolve())
257-    return s.replace("\\", "\\\\").replace(":", "\\:").replace("'", "\\'")
258-
259-def build_segments(manifest_path, output_path, cache_dir=None, ass_path=None, crf=18, preset="veryfast"):
260-    mp = Path(manifest_path)
--
333-            f"scale={TARGET_W}:{TARGET_H}:force_original_aspect_ratio=increase,"
334-            f"crop={TARGET_W}:{TARGET_H},setsar=1,fps={TARGET_FPS}"
335-        )
336-
337-        if ass_path:
338:            ap = ffmpeg_escape_ass(ass_path)
339-            vf += (
340-                f",setpts=PTS+{timeline:.6f}/TB,"
341-                f"ass='{ap}',"
342-                f"setpts=PTS-{timeline:.6f}/TB"
343-            )

===== /home/hermes/.hermes/skills/media/shorts-core/references/production-profiles.md =====
14-
15-Profil yalnız ton, hedef izleyici, görsel kaynak türü, anlatım performansı ve kalite önceliğini belirler. Sabit beat sırası, sabit saniye aralıkları veya her videoda tekrarlanan kompozisyon şablonu dayatmaz.
16-
17-`shorts-core/SKILL.md` içindeki **Evrensel Yönetmenlik ve Görsel Kanıt Standardı** bütün profillerin üst katmanıdır; hiçbir profil bu standardı gevşetemez.
18-
19:Her üretimde konuya göre anlatı mimarisini yeniden seç. Manifestte `creative_intent`, konu bağımsız `performance_plan`, kısa `directing_plan` ve her shot için `source_origin` + `visual_goal` alanlarını doldur. `directing_plan.beat_treatment`, kaynak aramasından önce her beat'in dramatik görevini, görünür eylemini, önceki beat'ten algılanabilir değişimini, ihtiyaç duyulan asset'i, geçiş motivasyonunu ve sonraki saniyeye açık soruyu çözer. Bir asset'in içinde gerçekten farklı görsel durumlar varsa bunları `visual_beats` ile belirt; semantic QA her anlamlı durumu aynı contact sheet'te görsün. Profil, performans/yönetmenlik planının tonunu etkileyebilir ama evrensel kalite kapısını gevşetemez.
20-
21-## Anlatı çeşitliliği
22-
23-Hook için tek varsayılan gramer kullanma.
24-
--
48-- Kaynak görüntüler arasında çözünürlük, netlik, renk ve çekim kalitesi farkı barizse düşük kaliteli olanı kullanma.
49-- Aynı footage tekrarını yalnız bilinçli callback ise kullan.
50-- Farklı dosya, farklı çekim demek değildir. Aynı merkez eksen, aynı ölçek, aynı ürün eylemi ve aynı enerji art arda geliyorsa `composition_family` etiketleri farklı olsa bile tekrar say.
51-- Aynı ürün/olay dünyasında geniş bağlam → fiziksel temas/detay → görünmeyen mekanizma → gerçek sonuç gibi algılanabilir bir ilerleme ara; bunu sabit beat sırası değil konuya özgü bir treatment olarak kur.
52-- Grafik kendi başına ayrı bir “AI kartı” gibi görünmemeli; mümkünse gerçek görüntünün üstünde/içinde uzamsal bağ kurmalı, tam ekran olmak zorundaysa eşleşen şekil/hareket/ses köprüsüyle footage'dan doğmalı ve tekrar footage'a dönmeli.
53:- Koyu rounded panel, büyük bölüm başlığı ve merkezde soyut ok/partikül kombinasyonunu varsayılan bilim dili yapma. Önce gerçek yüzey, kap, çatlak, iz, ölçü veya hareket üzerinde yerel annotation/track/freeze-frame çözümünü düşün; yalnız görünmeyen bilgi kadar grafik kullan.
54:- Final shot yalnız “temiz ürün”, sakin atmosfer veya sebebe dönen callback göstermesin; narration'ın payoff fiilini/sonucunu taşıyan videonun en güçlü konuya özgü görüntüsü olsun.
55-
56-## news
57-
58-Hedef: hızlı ama bağırmayan, güvenilir gündem anlatımı.
59-
--
86-- hızlanacak/yavaşlayacak bölümler;
87-- payoff cümlesinin icrası.
88-
89-TTS motoru bu özellikleri doğrudan desteklemiyorsa metni ve segmentleri buna göre düzenle. Sırf süre tutturmak için tüm metne tek hız değeri uygulayıp dümdüz okuma üretme.
90-
91:## Türkçe caption treatment
92-
93:Narration'ı sentezden önce yüksek sesle okunuyormuş gibi değerlendir. Bir cümle caption kartına ancak özne-yüklem, tamlayan-tamlanan, sıfat-isim ve fiil öbekleri doğal sınır veriyorsa bölünebilir. `Onu/bunu/şunu` gibi nesne zamirini taşıdığı fiilden ayırma. Algoritmanın karakter dengesi bu kararı veremez. Manifestteki `caption_semantic_units`, narration'ı kelime kaybetmeden doğal kartlara ayırır; gerekirse önce narration'ı kısa, konuşma diline uygun cümlelere yeniden yaz. `caption_keep_together` özel ad/terim sigortasıdır, treatment değildir.
94-
95-## Görsel kalite kapısı
96-
97:Bir asset yalnız konu doğru diye kabul edilmez.
98-
99-Kullanmadan önce:
100-- yeterli gerçek çözünürlük/netlik;
101-- ağır compression/bulanıklık yok;
102-- bariz sisli/washed-out görüntü yok;
--
104-- 9:16 crop sonrası ana özne korunuyor;
105-- diğer seçilmiş görüntülerle kalite seviyesi aşırı kopuk değil
106-
107-olduğunu doğrula.
108-
109:Zayıf asset final videoya girdikten sonra vision ile kurtarılmaya çalışılmamalı.
110-
111-## Grafik yerleşimi
112-
113-Programatik grafik/animation, altyazının ayrılmış güvenli bölgesine metin veya bilgi kutusu yerleştirmemeli.
114-
--
116-
117-Grafik metni ile altyazı aynı bilgiyi tekrar etmemeli.
118-
119-## QA sırası
120-
121:1. plan ve asset kalite kapısı;
122:2. narration + `performance_plan` ve caption planı;
123-3. deterministic layout/bbox preflight;
124:4. final render/mux;
125:5. final narration/audio çıktısını `performance_plan`, telaffuz, padding, loudness ve clipping açısından doğrulama;
126-6. shot/overlay beat’lerinin tamamını kapsayan semantic contact sheet;
127:7. final semantic Vision QA;
128-8. PASS sonrası görüntü değişikliği yok.
129-
130:Aynı değişmemiş contact sheet ve aynı soruyu sırf emin olmak için tekrarlama. Final görüntü değişirse önceki Vision sonucu artık final render için geçerli değildir; değişmiş artifact yeni semantic QA ile doğrulanmalıdır.
131-
132-<!-- HERMES_OPERATIONAL_GUARDRAILS_V090 -->
133-## Operational deterministic production guardrails — v0.9.0
134-
135:This is the operative implementation for the existing asset-quality, layout-safety and local-repair rules.
136-
137-1. Before a full visual render, run:
138-   `/home/hermes/.hermes/skills/media/shorts-core/scripts/production_guardrails.py preflight --manifest <manifest.json>`
139:   A hard FAIL blocks render. Do not ask vision to rescue deterministic failures.
140-
141:2. Programmatic graphics/annotations must be materialized only into the affected shot before canonical assembly and declared with `materialized: true` plus pixel `bbox`. Caption-zone intersection is a deterministic FAIL. Unmaterialized graphics must not trigger a hand-built full-master render.
142-
143:3. For first assembly and local visual repair, use the segment-cached canonical path:
144:   `/home/hermes/.hermes/skills/media/shorts-core/scripts/production_guardrails.py segment-build --manifest <manifest.json> --output <visual_master.mp4> [--subtitles-ass <captions.ass>]`
145:   Unchanged shot segments must be reused from the segment cache. A changed shot must not force unchanged segments to re-encode.
146-
147:4. Low-resolution/missing/invalid assets fail before render. Original asset identity must survive as `source_origin`. Frequent or adjacent same-origin reuse is a directing review signal, not an automatic failure; judge whether it serves intentional continuity/callback or creates repetitive, weak visual storytelling. Extreme washed/flat signal candidates require review.
148-
149:Source/SFX aramasında tek toplu arama ve tek seçim turu kullan. Footage'ın kullanılabilir gerçek sesi varsa önce onu değerlendir. Bir ücretsiz sağlayıcının sayfası engellenirse aynı asset için HTML/regex/dependency denemelerini zincirleme; eldeki yeterli ve lisanslı alternatife geç. Production sırasında `bs4`, `cv2`, font veya kurulu pipeline bileşenlerini keşif amacıyla tekrar probe etme.
150-
151:5. Final QA order remains authoritative:
152:   preflight → repairs → final render → semantic contact sheet → final semantic Vision QA → no visual change after PASS.
153-
154:   Aynı değişmemiş final artifact ve aynı QA sorusu sırf emin olmak için tekrar değerlendirilmez. Ancak final görüntü gerçekten değişirse önceki Vision sonucu geçersizdir ve değişmiş artifact yeniden semantic QA ile doğrulanır.

===== /home/hermes/.hermes/skills/media/shorts-core/references/montaj-fast-path.md =====
30-Ham Türkçe ses ve VTT:
31-
32-```bash
33-"$EDGE_TTS" --voice tr-TR-AhmetNeural --rate=+5% --pitch=-2Hz \
34-  --file narration.txt \
35:  --write-media assets/voiceover_raw.mp3 \
36:  --write-subtitles assets/voiceover_raw.vtt
37-```
38-
39-Baş/son padding ile WAV:
40-
41-```bash
42:"$FFMPEG" -y -v error -i assets/voiceover_raw.mp3 \
43-  -af 'adelay=180:all=1,apad=pad_dur=0.22' \
44:  -c:a pcm_s16le -ar 48000 -ac 2 assets/voiceover_padded.wav
45-```
46-
47-Zaman eşleme: VTT’deki tüm başlangıç/bitişlere `+0.180 s` ekle. İlk ve son sessizliği doğrula:
48-
49-```bash
50:"$FFMPEG" -hide_banner -i assets/voiceover_padded.wav \
51-  -af silencedetect=noise=-50dB:d=0.05 -f null -
52-```
53-
54-## Minimal Montaj Projesi
55-
56-```json
57-{
58-  "version": "0.2",
59-  "id": "<uuid-v4>",
60:  "status": "final",
61-  "projectType": "editing",
62-  "name": "<project-name>",
63-  "workflow": "animations",
64-  "editingPrompt": "<brief>",
65-  "runCount": 1,
66-  "sources": [
67:    {"id": "visuals", "src": "<abs>/assets/visuals.mp4", "type": "video", "name": "visuals.mp4"}
68-  ],
69-  "settings": {
70-    "resolution": [1080, 1920],
71-    "fps": 30,
72-    "colorSpace": "sdr_bt709",
73-    "language": "tr",
74-    "proxy": false
75-  },
76-  "tracks": [
77-    {"id": "trk-0", "items": [
78:      {"id": "visual-master", "type": "video", "src": "<abs>/assets/visuals.mp4",
79-       "start": 0.0, "end": 30.0, "inPoint": 0.0, "outPoint": 30.0, "muted": true}
80-    ]}
81-  ],
82:  "assets": [],
83-  "audio": {"tracks": [
84:    {"id": "voiceover", "src": "<abs>/assets/voiceover_padded.wav",
85-     "start": 0.0, "end": 30.0, "volume": 1.0, "fadeIn": 0.03, "fadeOut": 0.08},
86:    {"id": "music", "src": "<abs>/assets/music.m4a",
87-     "start": 0.0, "end": 30.0, "inPoint": 0.0, "outPoint": 30.0,
88-     "volume": 0.16, "fadeIn": 1.4, "fadeOut": 2.0,
89-     "ducking": {"enabled": true, "depth": -10, "attack": 0.25, "release": 0.6}}
90-  ]}
91-}
92-```
93-
94:## Assembly / Final Çıktı
95-
96-Hazır 1080×1920 görsel master üzerinde yalnız ses/müzik, gain/fade veya container işlemi gerekiyorsa **Montaj ile yeniden encode etme**. FFmpeg direct mux varsayılandır:
97-
98-```bash
99-"$FFMPEG" -y -v error \
100:  -i assets/visuals.mp4 \
101:  -i assets/voiceover_padded.wav \
102-  -map 0:v:0 -map 1:a:0 \
103-  -c:v copy \
104-  -c:a aac -b:a 192k \
105-  -movflags +faststart \
106:  final.mp4
107-```
108-
109-Video stream'in değişmediğini gerektiğinde doğrula:
110-
111-```bash
112:"$FFMPEG" -v error -i assets/visuals.mp4 -map 0:v:0 -c copy -f md5 -
113:"$FFMPEG" -v error -i final.mp4 -map 0:v:0 -c copy -f md5 -
114-```
115-
116-Timeline edit, gerçek overlay/composite veya Montaj'a özgü katman gerekiyorsa:
117-
118-```bash
119-"$MONTAJ" validate project project.json
120-"$HERMES_HOME/bin/montaj" render project.json --workers 2 --json
121-```
122-
123:## Preflight ve Final QA
124-
125:Final encode/render'dan önce subtitle bbox ve diğer deterministic guardrail'leri çalıştır. Safe area veya maksimum iki satır koşulu FAIL ise pahalı final render'a geçme. Değişmeyen guardrail/contact-sheet çıktısını cache'den kullan.
126-
127-Teknik probe:
128-
129-```bash
130-"$FFPROBE" -v error \
131-  -show_entries format=duration,size,bit_rate \
132-  -show_entries stream=index,codec_type,codec_name,width,height,r_frame_rate,nb_frames,channels,sample_rate \
133:  -of json final.mp4
134-```
135-
136:Siyah alan ve uzun freeze:
137-
138-```bash
139:"$FFMPEG" -hide_banner -i final.mp4 \
140:  -vf 'blackdetect=d=0.2:pix_th=0.02,freezedetect=n=-50dB:d=2.5' \
141-  -an -f null -
142-```
143-
144-Decode testi:
145-
146-```bash
147:"$FFMPEG" -v error -i final.mp4 -f null -
148-```
149-
150-Ses seviyesi:
151-
152-```bash
153:"$FFMPEG" -hide_banner -i final.mp4 -map 0:a:0 \
154-  -af 'loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json' -f null -
155-```
156-
157:Contact sheet’i rastgele değil, kanca + her cümle/animasyon + finalden kare seçerek üret. `vision_analyze` sorusu şu maddeleri açıkça istemeli:
158-
159-1. 9:16 tam ekran ve siyah/boş bant yok;
160-2. altyazı en fazla 2 satır;
161-3. metin kutusu dahil %8 safe area;
162-4. Türkçe karakter ve yazım;
163-5. açıklayıcı grafiklerin anlaşılabilirliği;
164-6. render bozulması veya yanlış crop;
165-7. sahne çeşitliliği ve anlatımla senkron.
166-
167:`freezedetect` çıktısında 2.5 saniyeden uzun freeze, `blackdetect` çıktısında siyah bölüm veya contact sheet’te taşma varsa teslimden önce düzelt.
root@213-238-170-219:~#