Record the source, not the destination, in the FVI header

The `fvi://` arm of `output()` passed the destination `.fvi` path as
`FviSink::create`'s `source_path`, so every index named itself as its
own source. `SourceInfo::default()` supplied the rest, making
`source.medium` always "file" and `source.title` always 0 — three
header members wrong, where FVI_FORMAT.md §6.2 defines `source` as
describing the input.

Beyond the wrong data, it made the output unreproducible: two machines
indexing identical bytes emitted different files purely from where
they wrote them, and a local filesystem path leaked into a shareable
file.

`output()` cannot see the source, so thread the provenance down from
the driver, which can: `mux_stream` derives a `SourceInfo` per
`MuxInput` arm and passes it through `drive_mux` to `output()`. Per the
one-method-per-action rule this is a signature change, not an
`output_with_source()` variant; the parameter is `Option<&SourceInfo>`
so a caller with no provenance declares none rather than back-filling
the destination. `SourceInfo`/`Medium` become public API.

What each arm can honestly reach:

- Session: everything — device path, the caller's title index, the
  title's playlist, the scanned volume id.
- Url: the source URL, its scheme's medium, `title_index`, and the
  playlist off the opened stream's scanned title.
- Iso: the image path and playlist. The title index is not in
  `MuxInput::Iso` (it carries a scanned `DiscTitle`, which has no
  index), so it stays 0.
- Live: medium and playlist. The reader is an opaque
  `Box<dyn SectorSource>` with no path, and again no title index.

Unreachable members are left empty rather than guessed — the sink
already omits the empty ones.
This commit is contained in:
Matthew Jackson
2026-08-02 11:19:54 -07:00
parent e008e71a17
commit cf7ee69fd5
9 changed files with 554 additions and 216 deletions
+2 -2
View File
@@ -136,7 +136,7 @@ fn open_input_bare_path_errors() {
#[test]
fn open_output_bare_path_errors() {
let dt = sample_disc_title();
let result = libfreemkv::output("Movie.mkv", &dt);
let result = libfreemkv::output("Movie.mkv", &dt, None);
assert!(result.is_err());
let msg = match result {
Err(e) => e.to_string(),
@@ -182,7 +182,7 @@ fn open_output_null_input_errors() {
#[test]
fn open_output_disc_errors() {
let dt = sample_disc_title();
let result = libfreemkv::output("disc://", &dt);
let result = libfreemkv::output("disc://", &dt, None);
assert!(result.is_err());
let msg = match result {
Err(e) => e.to_string(),