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:
+20
-4
@@ -11,7 +11,7 @@ use libfreemkv::disc::{
|
||||
VideoStream,
|
||||
};
|
||||
use libfreemkv::pes::Stream as PesStream;
|
||||
use libfreemkv::{DecryptKeys, SectorSource, build_iso_pipeline, output};
|
||||
use libfreemkv::{DecryptKeys, Medium, SectorSource, SourceInfo, build_iso_pipeline, output};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// DVD video PES stream_id (0xE0).
|
||||
@@ -182,8 +182,16 @@ fn run_to_fvi(image: Vec<u8>, title: DiscTitle, path: &std::path::Path) {
|
||||
)
|
||||
.expect("pipeline builds");
|
||||
|
||||
// Real provenance for the run — the header must describe THIS input, not
|
||||
// the `.fvi` it is writing.
|
||||
let source = SourceInfo {
|
||||
medium: Medium::Iso,
|
||||
path: "iso://two-gop.iso".into(),
|
||||
title: 2,
|
||||
..SourceInfo::default()
|
||||
};
|
||||
let url = format!("fvi://{}", path.display());
|
||||
let mut sink = output(&url, &title).expect("fvi sink opens");
|
||||
let mut sink = output(&url, &title, Some(&source)).expect("fvi sink opens");
|
||||
|
||||
while let Some(frame) = input.read().expect("read ok") {
|
||||
sink.write(&frame).expect("sink write ok");
|
||||
@@ -217,8 +225,16 @@ fn fvi_sink_indexes_real_mpeg2_pipeline_output() {
|
||||
assert_eq!(stream["colour"]["transfer"], 6);
|
||||
assert_eq!(stream["colour"]["matrix"], 6);
|
||||
assert_eq!(stream["colour"]["range"], "limited");
|
||||
// Provenance root: medium defaults to "file", sector_size present.
|
||||
// Provenance root describes the INPUT the index was built from — never the
|
||||
// destination `.fvi` (which is what the sink used to record).
|
||||
assert_eq!(header["source"]["sector_size"], 2048);
|
||||
assert_eq!(header["source"]["medium"], "iso");
|
||||
assert_eq!(header["source"]["path"], "iso://two-gop.iso");
|
||||
assert_eq!(header["source"]["title"], 2);
|
||||
assert!(
|
||||
!header["source"]["path"].as_str().unwrap().contains(".fvi"),
|
||||
"the destination path must never leak into source.path"
|
||||
);
|
||||
|
||||
// ── Records ───────────────────────────────────────────────────────────────
|
||||
let records: Vec<serde_json::Value> = lines.map(|l| serde_json::from_str(l).unwrap()).collect();
|
||||
@@ -324,7 +340,7 @@ fn fvi_sink_indexes_non_mpeg2_frames_codec_agnostically() {
|
||||
};
|
||||
|
||||
let url = format!("fvi://{}", path.display());
|
||||
let mut sink = output(&url, &title).expect("fvi sink opens");
|
||||
let mut sink = output(&url, &title, None).expect("fvi sink opens");
|
||||
// IDR (keyframe) with real provenance — must NOT be null/"?".
|
||||
sink.write(&mk(1234, true, Some(SourcePos::at_byte(8192))))
|
||||
.unwrap();
|
||||
|
||||
+2
-2
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user