Skip to content

Commit 2366080

Browse files
committed
fix(audio-postprocessing): declare mapper carrier removals
Signed-off-by: shbhawsar <shbhawsar@nvidia.com>
1 parent 5d1a3e0 commit 2366080

2 files changed

Lines changed: 64 additions & 0 deletions

File tree

‎nemo_curator/stages/audio/postprocessing/timestamp_mapper.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,8 @@ def describe(self) -> StageContract:
445445
),
446446
metadata_reads=[self.mappings_key],
447447
preserves_upstream_keys=False,
448+
# Output aliases can recreate a blocked input name, including on a conditional branch.
449+
removes_keys=sorted(_NEVER_PASS_KEYS - core_fields - {self.speaking_duration_key}),
448450
conditional_writes=conditional_writes,
449451
)
450452

‎tests/stages/audio/postprocessing/test_timestamp_mapper.py‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,68 @@ def test_strict_sanitization_removes_non_json_values_before_real_writer(tmp_path
643643
assert json.loads((tmp_path / "out.jsonl").read_text()) == mapped.data
644644

645645

646+
@pytest.mark.parametrize("carrier", sorted(_NEVER_PASS_KEYS))
647+
def test_blocked_tensor_carrier_is_removed_before_planned_json_writer(carrier: str, tmp_path: Path) -> None:
648+
mapper = TimestampMapperStage(passthrough_keys=[carrier])
649+
writer = ManifestWriterStage(output_path=str(tmp_path / "out.jsonl"))
650+
data = {"audio_filepath": "clip.wav", "duration": 1.0, carrier: torch.ones(4)}
651+
652+
report = validate_pipeline([mapper, writer], initial_keys=set(data), initial_tensor_keys={carrier})
653+
654+
assert report.ok
655+
output = mapper.process(_make_task(data))
656+
assert isinstance(output, AudioTask)
657+
assert carrier not in output.data
658+
writer.setup()
659+
writer.process(output)
660+
assert json.loads((tmp_path / "out.jsonl").read_text()) == output.data
661+
662+
663+
def test_retained_custom_tensor_still_blocks_planned_json_writer(tmp_path: Path) -> None:
664+
mapper = TimestampMapperStage(passthrough_keys=["embedding"])
665+
writer = ManifestWriterStage(output_path=str(tmp_path / "out.jsonl"))
666+
embedding = torch.ones(4)
667+
data = {"audio_filepath": "clip.wav", "duration": 1.0, "waveform": torch.ones(4), "embedding": embedding}
668+
669+
report = validate_pipeline([mapper, writer], initial_keys=set(data), initial_tensor_keys={"waveform", "embedding"})
670+
671+
assert not report.ok
672+
assert any(issue.code == "tensor_into_sink" for issue in report.issues)
673+
output = mapper.process(_make_task(data))
674+
assert isinstance(output, AudioTask)
675+
assert "waveform" not in output.data
676+
assert output.data["embedding"] is embedding
677+
writer.setup()
678+
with pytest.raises(TypeError, match="JSON serializable"):
679+
writer.process(output)
680+
681+
682+
@pytest.mark.parametrize(
683+
"alias_field",
684+
[
685+
"original_file_key",
686+
"original_start_ms_key",
687+
"original_end_ms_key",
688+
"duration_ms_key",
689+
"duration_key",
690+
"diar_segments_key",
691+
"speaking_duration_key",
692+
],
693+
)
694+
def test_blocked_name_recreated_by_output_alias_is_not_declared_removed(alias_field: str, tmp_path: Path) -> None:
695+
mapper = TimestampMapperStage(**{alias_field: "waveform"})
696+
data = {"audio_filepath": "clip.wav", mapper.diar_segments_key: [[0.125, 0.375]]}
697+
writer = ManifestWriterStage(output_path=str(tmp_path / "out.jsonl"))
698+
699+
assert "waveform" not in mapper.describe().removes_keys
700+
output = mapper.process(_make_task(data))
701+
assert isinstance(output, AudioTask)
702+
assert "waveform" in output.data
703+
writer.setup()
704+
writer.process(output)
705+
assert json.loads((tmp_path / "out.jsonl").read_text()) == output.data
706+
707+
646708
def test_legacy_positional_signature_is_preserved() -> None:
647709
signature = inspect.signature(TimestampMapperStage.__init__)
648710
assert [

0 commit comments

Comments
 (0)