Commit History
-
Raise Gradio request concurrency with tiered per-tab limits
John Ho +1: Claude Fable 5.1Gradio defaults every endpoint to one request at a time. Set concurrency_limit=8 on the image tabs and 2 on the video tabs (each video call spawns ffmpeg and holds the whole clip in RAM), configure app.queue() with a bounded max_size, and expose all three as PILBOX_* env vars so they can be raised on bigger hardware. Add tests/test_app.py for the wiring and env override; document in CLAUDE.md and README.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>96d7c0a
-
Expose text_overlay controls on the Annotate Video tab
John Ho +1: Claude Fable 5The optional caption from ffmpret.extract_frames is now reachable from the app: annotate_video_interface gains a Text overlay textbox (default empty = off) plus font-size slider and top/middle/bottom position dropdown, passed through app.py -> vidbox.annotate_video -> ffmpret.extract_frames. The caption is burned in at extraction time, so it renders beneath the box/mask annotations. The annotate-video-file CLI gains the matching --text-overlay/--text-font-size/ --text-y-position options, and the dropdown choices come from a new ffmpret.TEXT_Y_POSITIONS constant. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>be948dc -
Add text_overlay param to ffmpret.extract_frames
John Ho +1: Claude Fable 5A caller-supplied label is prefixed to the drawtext overlay line and is drawn even when write_timestamp=False, so it works standalone. The overlay text now reaches drawtext via a temp textfile= (removed in a finally) instead of inline text=, since ffmpeg 7's filtergraph parser cannot reliably express literal apostrophes or % inline; only the text expansion level remains, escaped by _drawtext_escape (\ and %). The template itself is built by _build_drawtext_text, unit-tested alongside an end-to-end special-character extraction test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>00ea497
-
Add box-format support to the Annotate (image) tab
John Ho +1: Claude Opus 4.8 (1M context)Add a "Box format" dropdown (boxer.BBOX_FORMATS, default pascal_voc) to the image Annotate tab, mirroring the Annotate Video tab. Each object's boundingBox (a {x0,y0,x1,y1} dict or a 4-number list) has its four ordered values reinterpreted per the chosen format and converted to pascal_voc via boxer.to_pascal_voc before pilbox.annotate. Conversion lives in app.py so pilbox stays lite; pascal_voc is the identity conversion, so existing behavior and the demo example are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>9c32855
-
Add Mask Video tab: per-frame background masking over video
John Ho +1: Claude Opus 4.8 (1M context)New vidbox.mask_video keeps each frame's masked foreground over a solid background color, driven by a per-frame mask JSON (the video counterpart of pilbox.apply_mask). Backs a new "Mask Video" tab in app.py (mask_video MCP tool) and a mask-video-file CLI. - Bounding boxes in the JSON are ignored; only masks are used. - One mask per frame: exact-duplicate rows dedupe, but two different masks on a frame raise ValueError (validated before ffmpeg runs). - Frames with no mask (MASK_GAP_BEHAVIORS): "skip" drops them (shorter, jump-cut output) or "fill" paints them entirely with the background color. The color drives both the mask background and fill frames. - Output keeps source resolution/fps; reuses ffmpret.extract_frames (fps=None) + frames_to_video and pilbox.apply_mask. Tests: mask_video dedupe-vs-conflict, skip-vs-fill frame counts, and an ffmpeg-gated e2e on the player1box reference asset. CLAUDE.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.co…9d3b191 -
changed the default for Crop mode
John Hod138c19 -
Add Crop Video tab: tracking-window video crop from a per-frame box JSON
John Ho +1: Claude Opus 4.8 (1M context)New vidbox.crop_video crops a video to a subject that moves across frames, driven by a per-frame detection JSON (masks ignored). Backs a new "Crop Video" tab in app.py (crop_video MCP tool) and a crop-video-file CLI. - Output is a constant-size silent video sized to the per-axis max box (x padding, even, clamped to the frame), so it follows the subject. - Two modes: "window" crops a fixed window from the frame re-centered on each box (keeps scene, pans); "box_fit" crops to the box, letterboxes to the output aspect, and resizes to fill (subject only). - One box per frame: exact-duplicate rows dedupe, but two different boxes on a frame raise ValueError (validated before ffmpeg runs). - Gap frames (no detection): "jump" (centered window / black frame) or "carry_forward" (repeat previous output), default jump. Supporting changes: - pilbox.letterbox(): aspect-preserving fit onto a solid canvas (backs box_fit), with a doctest. - app.py: Crop Video tab with video + JSON-file inp…8ebf17a
-
Add Annotate Video tab: per-frame box + mask annotation over video
John Ho +1: Claude Opus 4.8 (1M context)New vidbox.py orchestrates ffmpret (frame I/O) + boxer (format conversion) + pilbox (drawing) to annotate a video from a flat per-frame detection JSON, and backs a new "Annotate Video" tab in app.py (annotate_video MCP tool). Supports 4 box formats — pascal_voc, albumentations, coco, coco_normalized — converted to pascal_voc. Supporting changes: - boxer.py: add to_pascal_voc() format dispatcher (reuses get_bbox_dict for the coco variants); fix bbox_intersects to a standard AABB test (corner-in-rect checks missed "cross" overlaps, so get_bbox_iou wrongly returned 0); drop unused imports. - pilbox.annotate: optional shared color_map so a color_key value keeps one stable color across every frame (backward compatible). - ffmpret: extract_frames(fps=None) extracts every native frame (frame-exact for annotation); new frames_to_video() encoder. - app.py: video + JSON-file-upload inputs, format dropdown, mirrored label/color/mask/opacity/width/font controls; guarded example loader…c4e47f4 -
Add ffmpret.py video-extraction CLI (frames/metadata/audio)
John Ho +1: Claude Opus 4.8 (1M context)Standalone typer + ffmpeg-python CLI for pulling stills, metadata, and audio out of a video. Not yet wired into the Gradio app. Robustness fixes over the initial draft: - Stop piping ffmpeg stderr while only reading stdout (deadlocked on longer clips once the pipe buffer filled). - get_video_metadata: fall back to container duration when the video stream omits it (MKV/WebM), else frame counts were silently 0; and raise instead of returning None so callers fail legibly. - extract_frames: read until the pipe is exhausted rather than a fixed duration*fps estimate, so the tail frame isn't dropped. - parse_frame_name: rsplit on last "_" so underscored names round-trip. extract_audio: default mp3, add --lossless (acodec=copy into m4a); drop the leftover /tmp/miro default output dir. Remove dead get_fps_ffmpeg. Add tests/test_ffmpret.py and document in CLAUDE.md. Deps: ffmpeg-python + tqdm (needs a system ffmpeg binary). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthrop…71e110d -
Add Mask tab (background removal) and fix im_apply_mask imports
John Ho +1: Claude Opus 4.8 (1M context)New "Mask" tab: supply an image + a base64-encoded PNG mask and get the foreground cut out over a solid background color (default black), chosen via a color picker. Mirrors the existing Crop tab. - pilbox: fix im_apply_mask, which referenced ImageFilter/ImageOps without importing them (NameError in the bg-blur, bg-greyscale, and mask-blur paths); add both to the PIL import. Give it a Google docstring + doctest. - pilbox.apply_mask: new PIL-in/PIL-out wrapper decoding the base64 mask and raising ValueError on bad base64 or a size mismatch. - app.py: mask_image callback + Mask tab (api_name="mask") with a ColorPicker background; _rgb_from_css handles #rrggbb and rgba(...) values. ColorPicker is a string in the API/MCP schema, so the docstring states the expected CSS hex format; MCP tool schema verified intact. - tests: cover apply_mask, the im_apply_mask color path, shape-mismatch, and a regression guard for the blur/greyscale/mask-blur paths. Co-Authored-By: Claude Opus …d544d5d -
Add mask annotation to Annotate tab, colored to match boxes
John Ho +1: Claude Opus 4.8 (1M context)Overlay per-object segmentation masks (base64-PNG under `b64_mask`) beneath each bounding box, using the same color as the box so an object's mask and box always match. Masks draw whenever the mask key is present; clearing the key disables them. - pilbox.im_color_mask: fix int64->uint8 mask coercion (Image.fromarray rejected the bool*int product), fix shape guard (shape[:1] -> shape[:2]), rename rbg_tup -> rgb_tup, add Google docstring + doctest. - pilbox.annotate: add mask_key/mask_alpha; pre-assign colors per object so masks (drawn first, underneath) and boxes share one color. - app.py: expose Mask key + Mask opacity inputs on the Annotate tab; MCP tool schema verified intact. - annotate_cli.py / annotate_file: add --mask-key / --mask-alpha parity. - tests: cover the bug fixes and box/mask color consistency. - pyproject/uv.lock: add pytest as a dev dependency. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>93754f2
-
Add image-crop feature to the Gradio app
John Ho +1: Claude Opus 4.8 (1M context)Add a pilbox.crop(image, x0, y0, x1, y1) wrapper around im_crop that validates the pascal_voc box, converts to RGB, and returns a new image without mutating the input. Restructure app.py into a gr.TabbedInterface with Annotate and Crop tabs, each exposed as its own MCP/API endpoint (api_name "annotate" / "crop"). Crop takes manual x0/y0/x1/y1 inputs and surfaces invalid boxes as a gr.Error. Also tighten both MCP tool docstrings so Gradio's schema parser keeps the full box-format detail (pascal_voc reference URL, single-line args). Add pilbox.crop tests and document the feature in CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>b6ebd4d
-
fixed typo
John Ho182506e -
created a very minimal requirements.txt to skip uv's generation
John Ho0d6f6ca -
Untrack example JSON assets; make app example fully optional
John Ho +1: Claude Opus 4.8 (1M context)- git rm --cached the two assets/*.json files and gitignore assets/*.json; kept on disk for local use, not deployed to the Space (text, so no history rewrite needed unlike the JPGs). - _load_example() now treats both the image and JSON as optional and logs at INFO (not WARNING) when they're absent, since that's the normal Space state. - Update CLAUDE.md deployment note to cover the JSON assets too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>4ede126 -
Document that binary assets are gitignored, not deployed to HF
John Ho2bbba53 -
Ignore image/binary assets (kept on disk, not committed)
John Hod54d99b -
Make HF deploy: skip reqs gen if present, opt-in force push
John Ho +1: Claude Opus 4.8 (1M context)- Generate requirements.txt only when it doesn't already exist, so a hand-maintained file is left untouched. - Commit the generated requirements.txt so the push actually carries it. - Force-push to the Space only when the FORCE_PUSH secret is set. - Update CLAUDE.md Deployment section to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>4c59d22 -
update hfs git url
John Ho51f2ca9 -
Rewrite Gradio app as a pilbox bbox annotation tool
John Ho +1: Claude Opus 4.8 (1M context)Replace the copied video-captioning VLM demo (torch + transformers + ZeroGPU spaces + flash-attn, four models loaded at import, and a broken fn=inference reference) with a bounding-box annotation app that mirrors annotate_cli.py. - app.py: single gr.Interface whose callback delegates to pilbox.annotate; inputs mirror the CLI (image, pascal_voc JSON, label_key, color_key, width, font_size); invalid JSON -> gr.Error; example loaded from assets/; MCP server + /docs launch retained - deps: drop transformers/pydantic/qwen-vl-utils (removes torch, spaces, flash-attn stack), add explicit numpy + pillow, keep gradio[mcp] extra for mcp_server=True - CLAUDE.md: update the app description accordingly Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>e2ac00e -
Turn pilbox into a lite bbox annotation module with a CLI
John Ho +1: Claude Opus 4.8 (1M context)Rewrite pilbox.py as a standalone, importable bounding-box annotation module depending only on numpy + Pillow + loguru (drop the broken matplotlib.colors reference and add a proper import block). - annotate(): main entry point using pascal_voc {x0,y0,x1,y1} boxes, returns an annotated copy, configurable label/color keys - palette_color()/color_for(): unbounded golden-angle HSV palette replacing the matplotlib color logic - im_draw_bbox(): filled label tab above the box top-left corner - keep im_crop/im_center_crop/im_draw_point/load_pil_font for later Add annotate_cli.py (typer + loguru) to annotate an image from a JSON file, tests/ with unit tests + doctests, example assets, and update CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>df4e114
-
recruited the help of claude
John Ho21ec205 -
Initial commit
John Ho4e3ab14