Skip to content

v0.0.6

Choose a tag to compare

@MagicalTux MagicalTux released this 01 Oct 03:20
· 36 commits to master since this release
2b0eaaa

Added

  • (convert) frame tap — --opt encoder options, .avif => codec=av1, %d per-frame fan-out
  • (convert) still-image sink planning — primary-still stream pin + lossless-narrowest encoder layout
  • (convert) --help / -help usage synopsis generated from the parser's flag table
  • (convert) arg-parsing hardening — '--' end-of-flags, empty-arg rejection, flag-table sync test
  • (convert) golden plan snapshots + noise-free f32 JSON + shared op-lowering
  • (convert) schema-validation guarantee on every emitted job + JSON round-trip pinning
  • (convert) extract pure routing layer with matrix-pinned dispatch
  • (convert) plan_to_render3d_job() for 3D→raster auto-route (Phase C-3b)

Fixed

  • (convert) -thumbnail seeds the 3D render canvas + %d template rejected on non-fanout routes

Other

  • (convert) where the time goes on 12 MP HEIC -> PNG — stage attribution table, before/after, remaining owners
  • (convert) pin still-image sink output content before the r463 image-path work
  • hide internal pub surface from rustdoc/semver (fleet rule 2026-09-01)
  • (convert) fix two stale README claims against current behaviour
  • (convert) adversarial-argv no-panic corpus + CHANGELOG rollup + planning-guarantees docs
  • follow oxideav-pdf 0.2 (major-bump pin propagation)
  • add CI / crates.io / docs.rs / MIT-license badges
  • refresh to current status, drop per-round changelog cruft
  • drop release-plz.toml — use release-plz defaults across the workspace

Added

  • Still-image sink planning (image_sink module). When the output
    resolves to an image encoder and the input's container header can be
    read, the planner (a) pins the track to one video stream — HEIF /
    AVIF files carrying an image-sequence track open as stream 0 = the
    primary still plus one stream per track, and the single-image muxers
    used to refuse the second stream (.heics / .avis → .png now
    writes the primary still, with a stderr note); and (b) for a plain
    conversion, pins the encoder input to the accepted layout that loses
    nothing the source carries (colour, alpha, bit depth) at the fewest
    bits per pixel, via an explicit convert node. YUV → PNG is now RGB
    instead of RGBA with a synthetic opaque alpha (identical colour
    samples, 25% less data through deflate: 12 MP 8-bit HEIC → PNG CPU
    5.7 s → 4.4 s); >8-bit YUV → PNG keeps its precision as 16-bit RGB.

  • Frame tap (frame_tap module) — convert's own encode + mux for
    still-image outputs, fed decoded frames by the executor through the
    reserved @out sink. Used when the plan needs it:

    • --opt KEY=VALUE (repeatable): encoder options, validated at plan
      time against the target encoder's declared options schema
      (unknown key → typed error listing the known ones; values checked
      against the declared kind) and handed to the encoder. The
      pipeline's track codec_params never reach the encoder, so this
      is the only working option channel on the convert path.
    • .avif / .avifs outputs infer codec=av1 for the heif
      encoder (they were written as HEVC with a heic brand).
    • %d output templates on the pipeline route fan out one file per
      frame (0-based): image sequences (the first non-still stream —
      HEIF / AVIF put the primary still at stream 0), animations,
      video. Filter ops compose; the frame geometry after them is read
      off the frames.
      Same encoder settings produce byte-identical files on both paths.
  • OXIDEAV_CONVERT_TIMING=1 — per-stage wall-clock spans on stderr
    for the pipeline route (plan incl. header probe, executor; on the
    frame-tap path first-frame arrival, encode, mux + write).

  • -depth 8|16 — per-channel output depth for still-image sinks
    (a 10-bit HEIC → -depth 8 → 8-bit PNG; 12 MP: 3.6 s CPU).

  • Netpbm extensions pick their family: .pbm bilevel, .pgm
    grayscale, .ppm colour (no alpha); .pam keeps any layout.

  • route module — pure routing layer. route::decide(&ConvertPlan) -> Result<Route, Error> classifies a plan into its execution path
    (Help / Ping / Probe / PdfSideChannel / Mesh3dToMesh3d /
    Mesh3dToRaster / IcoOutput / Pipeline, feature-gated variants
    matching the cargo features) without performing any work; run()
    is now a thin dispatcher over it. Route is re-exported at the
    crate root. The full media-kind × media-kind dispatch matrix is
    pinned by IO-free unit tests, including precedence rules (help >
    ping > probe > input classification > output classification).

  • --help / -help — usage-synopsis mode, recognised anywhere on
    the line, outranks every other mode. args::usage() generates the
    flag list from the parser's own KNOWN_FLAG_NAMES table (correct
    dash flavour per flag) so the help text cannot drift from what the
    parser accepts. ConvertPlan grows a help: bool field.

  • -- end-of-flags marker — everything after it is a positional even
    when it starts with a dash, so files literally named -weird.png
    are expressible.

  • Schema-validation guarantee: plan_to_job and
    plan_to_render3d_job run the pipeline's own Job::validate()
    before returning, so planner bugs surface as typed errors at plan
    time. Pinned by tests over a 33-entry representative op corpus,
    together with a byte-stable JSON round-trip guarantee (serialise →
    re-parse → re-serialise identical) that makes convert-planned jobs
    storable / replayable as oxideav run documents.

  • Golden plan snapshots (tests/plan_golden.rs): byte-exact job JSON
    pinned for representative argv lines end-to-end (bare transcode,
    filter chain with sink params, -monochrome unroll, codec-less
    video container, full-option 3D render), plus determinism checks.

  • Flag-table sync test: every KNOWN_FLAG_NAMES entry is driven
    through the parser with a minimal valid value and must parse Ok,
    in both directions (stale table entry fails; accepted flag missing
    from the table fails).

  • Adversarial argv corpus: args::parse must return typed errors,
    never panic, across malformed geometry / selectors / colours /
    numbers / unicode / oversized tokens.

  • plan_to_render3d_job(plan, ctx) -> Result<Job, Error> — public
    helper that lowers a 3D-asset ConvertPlan into an
    oxideav_pipeline::Job whose track input is a
    TrackInput::Render3D source node. The pipeline executor hands the
    source URI + backend name + opaque opts JSON to a caller-installed
    RenderSourceFactory; cli-convert exposes the helper so the
    consumer-side wiring (next-phase CLI dispatch) can route 3D inputs
    through the same pipeline executor the regular pipeline path uses,
    rather than the legacy side-channel runner. Op::Resize and
    Op::Background are absorbed by the renderer (the canvas is produced
    at the requested size directly with the requested clear colour);
    every other op survives as a filter node wrapping the Render3D
    source, so the post-rasterisation tonal / geometry ops keep their
    existing JSON dialect. Mesh3DOptions::bg wins over an Op::Background
    in source order, mirroring mesh3d_render::pick_render_bg's
    precedence. The opts JSON shape mirrors
    oxideav_render::RenderOptions one-for-one (width, height,
    background, shading, projection, fov_deg, light, camera,
    aa) so the consumer's RenderSourceFactory can
    serde_json::from_value it straight into the typed struct.

  • RENDER3D_DEFAULT_BACKEND — public &'static str constant naming
    the default backend ("scanline") handed to Render3DNode::backend.
    Mirrors the only RenderBackend variant the workspace ships today;
    when raycast / pathtrace backends land, a -render-backend NAME
    arg will override the default via the same field.

  • 15 new unit tests in plan_to_job::tests covering: default plan +
    options producing a 1024×1024 scanline source with transparent black
    background; -resize WxH absorbed into render dims (no surviving
    video.resize filter); -background COLOR absorbed into render
    background (no surviving filter); -bg option overriding
    -background op; every Mesh3DRenderMode round-tripping through
    opts["shading"] as a stable lowercase tag (with normal-debug /
    depth-debug dash-cased to match the existing JSON dialect);
    ProjectionMode round-trip; -light triple round-trip;
    -camera triple round-trip; -fov DEGREES round-trip; -aa N
    round-trip with clamping to 1..=8 (matching
    RenderOptions::validate); post-rasterisation ops (rotate / negate
    / sharpen / strip / quality) surviving as filter nodes wrapping the
    Render3D leaf in source order; Op::Thumbnail absorbing the resize
    half and setting strip-metadata on codec params; output codec
    resolving through ContainerRegistry; -format FMT override
    threading through to codec params; -define KEY=VALUE landing on
    codec params; the opts payload carrying every documented field.

Fixed

  • YUV sources → .ppm / .pgm / .pam no longer come out as a 1-bit
    thresholded bitmap: the pipeline's default picked the Netpbm
    encoder's first accepted layout (MonoBlack) for any source it did
    not accept as-is.

  • .jpg / .jpeg outputs resolve to the mjpeg codec (the jpeg
    container's codec id) instead of failing with "codec not found:
    jpeg".

  • f32 op values no longer widen to f64 bit-patterns in the emitted
    job JSON: -light 10,20,0.9 used to serialise as
    "intensity": 0.8999999761581421, -blur 1x2.3 as
    "sigma": 2.299999952316284. Values now serialise via the f32's
    shortest decimal representation (the number the user typed);
    -modulate hue→degrees translates in f64 on the clean decimal so
    round percentages give round degrees (150 → 90.0).

  • -thumbnail WxH now seeds the 3D render canvas on both the
    auto-route planner (pick_render3d_dims) and the side-channel
    renderer (mesh3d_render::pick_dims); previously the geometry was
    absorbed but the dims dropped, silently rendering at the 1024×1024
    default. Last geometry op (-resize or -thumbnail) in source
    order wins.

  • A %d printf template on the regular pipeline or .ico routes is
    now a typed unsupported error instead of being written through as
    a literal output filename (template fan-out only exists on the PDF
    side-channel today).

  • Empty positional arguments (typically an unset shell variable) are
    rejected at parse time with an actionable message instead of
    surfacing as an unopenable "" filename deep inside a runner; an
    empty input path is also rejected at plan time by the new
    validation hook.

Changed

  • The ~340-line op-lowering walk duplicated between plan_to_job and
    plan_to_render3d_job is now one shared lower_op (with an
    absorb_canvas flag for the render path) plus a shared
    finish_job tail — float handling and future op additions happen
    in exactly one place.