import pytest from unshackle.core.manifests.ism import ISM from unshackle.core.tracks import Audio MANIFEST = """ """ def test_placeholder_spellings_repeat_and_audiotag_fallback() -> None: tracks = ISM.from_text(MANIFEST, "https://cdn.example/x.ism/manifest").to_tracks() (audio,) = tracks.audio segments = audio.data["ism"]["segments"] assert segments == [ "https://cdn.example/x.ism/QualityLevels(128000)/Fragments(audio=0)", "https://cdn.example/x.ism/QualityLevels(128000)/Fragments(audio=20000000)", "https://cdn.example/x.ism/QualityLevels(128000)/Fragments(audio=40000000)", ] assert audio.codec == Audio.Codec.AAC # AudioTag 255 means AACL def test_duration_fallback_when_next_fragment_omits_time() -> None: # A d-less whose successor also omits t must fall back to the manifest # duration (int(None) used to raise TypeError past the except clause). sparse = MANIFEST.replace('', '') tracks = ISM.from_text(sparse, "https://cdn.example/x.ism/manifest").to_tracks() assert tracks.audio[0].data["ism"]["segments"] == [ "https://cdn.example/x.ism/QualityLevels(128000)/Fragments(audio=0)", "https://cdn.example/x.ism/QualityLevels(128000)/Fragments(audio=60000000)", ] def test_missing_language_without_fallback_raises() -> None: # MS-SSTR Language is optional; video StreamIndexes commonly omit it. no_lang = MANIFEST.replace(' Language="en"', "") with pytest.raises(ValueError, match="fallback language"): ISM.from_text(no_lang, "https://cdn.example/x.ism/manifest").to_tracks() tracks = ISM.from_text(no_lang, "https://cdn.example/x.ism/manifest").to_tracks(language="en") assert str(tracks.audio[0].language) == "en" def test_live_manifest_rejected() -> None: live = MANIFEST.replace('Duration="60000000"', 'Duration="60000000" IsLive="TRUE"', 1) with pytest.raises(ValueError, match="Live"): ISM.from_text(live, "https://cdn.example/x.ism/manifest").to_tracks()