Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions scenedetect/output/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -726,19 +726,8 @@ def write_scene_list_otio(
"DEFAULT_MEDIA": {
"OTIO_SCHEMA": "ExternalReference.1",
"name": video_base_name,
"available_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": fps,
"value": 1980.0,
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": fps,
"value": 0.0,
},
},
# Scene ranges do not tell us the full media duration.
"available_range": None,
"available_image_bounds": None,
"target_url": video_abs_path,
}
Expand Down
84 changes: 6 additions & 78 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1101,19 +1101,7 @@ def test_cli_save_otio(tmp_path: Path):
"DEFAULT_MEDIA": {
"OTIO_SCHEMA": "ExternalReference.1",
"name": "goldeneye.mp4",
"available_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976023976023978,
"value": 1980.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976023976023978,
"value": 0.0
}
},
"available_range": null,
"available_image_bounds": null,
"target_url": "{ABSOLUTE_PATH}"
}
Expand Down Expand Up @@ -1141,19 +1129,7 @@ def test_cli_save_otio(tmp_path: Path):
"DEFAULT_MEDIA": {
"OTIO_SCHEMA": "ExternalReference.1",
"name": "goldeneye.mp4",
"available_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976023976023978,
"value": 1980.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976023976023978,
"value": 0.0
}
},
"available_range": null,
"available_image_bounds": null,
"target_url": "{ABSOLUTE_PATH}"
}
Expand Down Expand Up @@ -1189,19 +1165,7 @@ def test_cli_save_otio(tmp_path: Path):
"DEFAULT_MEDIA": {
"OTIO_SCHEMA": "ExternalReference.1",
"name": "goldeneye.mp4",
"available_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976023976023978,
"value": 1980.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976023976023978,
"value": 0.0
}
},
"available_range": null,
"available_image_bounds": null,
"target_url": "{ABSOLUTE_PATH}"
}
Expand Down Expand Up @@ -1229,19 +1193,7 @@ def test_cli_save_otio(tmp_path: Path):
"DEFAULT_MEDIA": {
"OTIO_SCHEMA": "ExternalReference.1",
"name": "goldeneye.mp4",
"available_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976023976023978,
"value": 1980.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976023976023978,
"value": 0.0
}
},
"available_range": null,
"available_image_bounds": null,
"target_url": "{ABSOLUTE_PATH}"
}
Expand Down Expand Up @@ -1319,19 +1271,7 @@ def test_cli_save_otio_no_audio(tmp_path: Path):
"DEFAULT_MEDIA": {
"OTIO_SCHEMA": "ExternalReference.1",
"name": "goldeneye.mp4",
"available_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976023976023978,
"value": 1980.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976023976023978,
"value": 0.0
}
},
"available_range": null,
"available_image_bounds": null,
"target_url": "{ABSOLUTE_PATH}"
}
Expand Down Expand Up @@ -1359,19 +1299,7 @@ def test_cli_save_otio_no_audio(tmp_path: Path):
"DEFAULT_MEDIA": {
"OTIO_SCHEMA": "ExternalReference.1",
"name": "goldeneye.mp4",
"available_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976023976023978,
"value": 1980.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976023976023978,
"value": 0.0
}
},
"available_range": null,
"available_image_bounds": null,
"target_url": "{ABSOLUTE_PATH}"
}
Expand Down
26 changes: 26 additions & 0 deletions tests/test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,3 +654,29 @@ def test_write_scene_list_otio_output_target(tmp_path: Path, target_kind: str):
assert "\r\n" not in text
doc = json.loads(text)
assert doc["OTIO_SCHEMA"] == "Timeline.1"


@pytest.mark.parametrize("frame_rate", [_FPS_CFR, _FPS_NTSC])
@pytest.mark.parametrize("audio", [False, True])
@pytest.mark.parametrize("ranges", [[], [(0, 24)], [(2400, 4800)], [(24, 72), (3000, 3600)]])
def test_write_scene_list_otio_unknown_available_range(tmp_path, frame_rate, audio, ranges):
"""Scene ranges do not reveal the source video's full available duration."""
output_path = tmp_path / "scenes.otio"
write_scene_list_otio(
output_path,
scene_list=_fake_scenes(frame_rate, ranges),
video_path=tmp_path / "source.mp4",
frame_rate=frame_rate,
audio=audio,
)

doc = json.loads(output_path.read_text())
tracks = doc["tracks"]["children"]
assert len(tracks) == (2 if audio else 1)
for track in tracks:
assert len(track["children"]) == len(ranges)
for clip, (start, end) in zip(track["children"], ranges, strict=True):
ref = clip["media_references"]["DEFAULT_MEDIA"]
assert ref["available_range"] is None
assert clip["source_range"]["start_time"]["value"] == pytest.approx(start)
assert clip["source_range"]["duration"]["value"] == pytest.approx(end - start)
4 changes: 4 additions & 0 deletions website/pages/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

## PySceneDetect 0.7

### Unreleased

- [bugfix] OTIO exports leave the source media available range unspecified instead of declaring a fixed 1,980-frame duration. Scene source ranges are unchanged.

### PySceneDetect 0.7.2 (October 11, 2026)

PySceneDetect 0.7.2 ships improved defaults for the histogram and perceptual hash detectors, adds PyAV 19 support, and fixes several timing and output bugs. **Default output of `detect-hist` and `detect-hash` will change in this release due to improved default values**.
Expand Down