{"record":{"id":"67e1d05429f927a5","repo":"affaan-m/ECC","slug":"output-width-and-height-must-be-positive","errorCode":null,"errorMessage":"output width and height must be positive","messagePattern":"output width and height must be positive","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/forge.py","lineNumber":91,"sourceCode":"        raise ValueError(\"base video must be a readable local file\")\n    if any(not Path(o).is_file() for o in (overlays or [])):\n        raise ValueError(\"all overlays must be readable local files\")\n    if fps is not None and (not math.isfinite(fps) or fps <= 0):\n        raise ValueError(\"fps must be finite and positive\")\n    sp = pack_mod.load(genre, root=root)\n    tgt = grade_mod.load_stats(sp.grade_path)\n    cad = cad_mod.load(sp.cadence_path)\n\n    # Geometry comes from the first take unless overridden; everything else is\n    # normalized to it so concat does not silently fail on a size mismatch.\n    info0 = frame_mod.probe(takes[0])\n    W = width or info0.width\n    H = height or info0.height\n    FPS = fps if fps is not None else info0.fps\n    if not math.isfinite(FPS) or FPS <= 0:\n        raise ValueError(\"source fps must be finite and positive; provide --fps\")\n    if W <= 0 or H <= 0:\n        raise ValueError(\"output width and height must be positive\")\n    # Prior timelines reference these shot files. Each run owns a fresh child,\n    # including failed runs, so retries cannot erase an existing edit.\n    work_root = Path(work)\n    work_root.mkdir(parents=True, exist_ok=True)\n    work_dir = Path(tempfile.mkdtemp(prefix=\"run-\", dir=work_root)).resolve()\n    print(f\"forging '{genre}'  ->  {W}x{H} @ {FPS:g}fps\")\n    print(f\"  cadence: mean {cad.mean_shot:.2f}s, {cad.cuts_per_min:.0f} cuts/min, \"\n          f\"variance {cad.rhythm_variance:.2f}\")\n\n    total_target = duration or sum(frame_mod.probe(t).duration for t in takes)\n    # When apply.py generated these takes it already decided where the cuts\n    # fall, and it told the model so (\"cut into 6 pieces of ~0.8s\"). Re-planning\n    # here would silently cut somewhere else, against footage shot for the\n    # original plan - so the caller's plan wins when there is one.\n    if plan is None:\n        # Plan PER TAKE against each take's own length, not by splitting the\n        # target across an arbitrary number of groups.\n        #","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/forge.py#L73-L109","documentation":"forge() derives output geometry from the first take's probe unless width/height overrides are given. If the computed W or H ends up zero or negative (bad probe metadata or a nonsensical override), it raises ValueError because ffmpeg cannot produce a valid video at that size.","triggerScenarios":"Calling forge(..., width=0) or height=-1; probing a take whose stream reports 0x0 dimensions (corrupt file, audio-only file passed as a take, exotic raw container).","commonSituations":"Passing an audio file or image-less stream as takes[0]; width/height parsed from config as 0; ffprobe failing to read display dimensions from a damaged recording.","solutions":["Pass explicit positive dimensions: forge(..., width=1920, height=1080) or --width/--height on the CLI.","Verify the first take is a real video with dimensions: ffprobe -select_streams v:0 -show_entries stream=width,height take0.mov.","Reorder takes so a healthy video file is first, or re-encode the corrupt take.","Fix the config/override values so width and height are > 0."],"exampleFix":"# before\nforge('noir', takes, width=int(cfg['width']), out='edit.mp4')  # cfg width = 0\n\n# after\nW = max(int(cfg.get('width', 0)), 1)\nH = max(int(cfg.get('height', 0)), 1)\nforge('noir', takes, width=W, height=H, out='edit.mp4')","handlingStrategy":"validation","validationCode":"import subprocess, json\n\ndef probe_dims(path):\n    out = subprocess.run(['ffprobe', '-v', 'error', '-select_streams', 'v:0',\n                          '-show_entries', 'stream=width,height', '-of', 'json', path],\n                         capture_output=True, text=True)\n    s = json.loads(out.stdout)['streams'][0]\n    return int(s['width']), int(s['height'])  # caller asserts both > 0","typeGuard":"def has_valid_dims(info):\n    return info.width > 0 and info.height > 0","tryCatchPattern":"try:\n    forge(genre, takes, out=out)\nexcept ValueError as e:\n    if 'width and height' in str(e):\n        forge(genre, takes, width=1920, height=1080, out=out)","preventionTips":["Pass explicit --width/--height when take metadata is suspect.","Ensure takes[0] is a real video with video streams, not audio-only or corrupt.","Probe dimensions of the first take before forging and assert they are positive."],"tags":["video","dimensions","validation","ffprobe"],"backgroundTag":"invalid-argument-value","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}