117 lines
5.1 KiB
Python
Raw Permalink Normal View History

2025-03-18 00:17:27 +05:30
import base64
import re
from hashlib import md5
from vinetrimmer.objects import AudioTrack, TextTrack, Track, Tracks, VideoTrack
2025-03-18 00:23:51 +05:30
from vinetrimmer.utils import Cdm
2025-03-18 00:17:27 +05:30
from vinetrimmer.vendor.pymp4.parser import Box
def parse(master, source=None):
"""
Convert a Variant Playlist M3U8 document to a Tracks object with Video, Audio and
Subtitle Track objects. This is not an M3U8 parser, use https://github.com/globocom/m3u8
to parse, and then feed the parsed M3U8 object.
:param master: M3U8 object of the `m3u8` project: https://github.com/globocom/m3u8
:param source: Source tag for the returned tracks.
The resulting Track objects' URL will be to another M3U8 file, but this time to an
actual media stream and not to a variant playlist. The m3u8 downloader code will take
care of that, as the tracks downloader will be set to `M3U8`.
Don't forget to manually handle the addition of any needed or extra information or values.
Like `encrypted`, `pssh`, `hdr10`, `dv`, e.t.c. Essentially anything that is per-service
should be looked at. Some of these values like `pssh` and `dv` will try to be set automatically
if possible but if you definitely have the values in the service, then set them.
Subtitle Codec will default to vtt as it has no codec information.
Example:
tracks = Tracks.from_m3u8(m3u8.load(url))
# check the m3u8 project for more info and ways to parse m3u8 documents
"""
if not master.is_variant:
raise ValueError("Tracks.from_m3u8: Expected a Variant Playlist M3U8 document...")
# get pssh if available
# uses master.data.session_keys instead of master.keys as master.keys is ONLY EXT-X-KEYS and
# doesn't include EXT-X-SESSION-KEYS which is whats used for variant playlist M3U8.
keys = [x.uri for x in master.session_keys if x.keyformat.lower() == "com.microsoft.playready"]
pssh = keys[0].split(",")[-1] if keys else None
# if pssh:
# pssh = base64.b64decode(pssh)
# # noinspection PyBroadException
# try:
# pssh = Box.parse(pssh)
# except Exception:
# pssh = Box.parse(Box.build(dict(
# type=b"pssh",
# version=0, # can only assume version & flag are 0
# flags=0,
# system_ID=Cdm.uuid,
# init_data=pssh
# )))
return Tracks(
# VIDEO
[VideoTrack(
id_=md5(str(x).encode()).hexdigest()[0:7], # 7 chars only for filename length
source=source,
url=("" if re.match("^https?://", x.uri) else x.base_uri) + x.uri,
# metadata
codec=x.stream_info.codecs.split(",")[0].split(".")[0], # first codec may not be for the video
language=None, # playlists don't state the language, fallback must be used
bitrate=x.stream_info.average_bandwidth or x.stream_info.bandwidth,
width=x.stream_info.resolution[0],
height=x.stream_info.resolution[1],
fps=x.stream_info.frame_rate,
hdr10=(x.stream_info.codecs.split(".")[0] not in ("dvhe", "dvh1")
and (x.stream_info.video_range or "SDR").strip('"') != "SDR"),
2025-03-18 00:23:51 +05:30
hlg=False, # TODO: Can we get this from the manifest?
2025-03-18 00:17:27 +05:30
dv=x.stream_info.codecs.split(".")[0] in ("dvhe", "dvh1"),
# switches/options
descriptor=Track.Descriptor.M3U,
# decryption
encrypted=bool(master.keys or master.session_keys),
pssh=pssh,
# extra
extra=x
) for x in master.playlists],
# AUDIO
[AudioTrack(
id_=md5(str(x).encode()).hexdigest()[0:6],
source=source,
url=("" if re.match("^https?://", x.uri) else x.base_uri) + x.uri,
# metadata
codec=x.group_id.replace("audio-", "").split("-")[0].split(".")[0],
language=x.language,
bitrate=0, # TODO: M3U doesn't seem to state bitrate?
channels=x.channels,
atmos=(x.channels or "").endswith("/JOC"),
descriptive="public.accessibility.describes-video" in (x.characteristics or ""),
# switches/options
descriptor=Track.Descriptor.M3U,
# decryption
encrypted=False, # don't know for sure if encrypted
pssh=pssh,
# extra
extra=x
) for x in master.media if x.type == "AUDIO" and x.uri],
# SUBTITLES
[TextTrack(
id_=md5(str(x).encode()).hexdigest()[0:6],
source=source,
url=("" if re.match("^https?://", x.uri) else x.base_uri) + x.uri,
# metadata
codec="vtt", # assuming VTT, codec info isn't shown
language=x.language,
forced=x.forced == "YES",
sdh="public.accessibility.describes-music-and-sound" in (x.characteristics or ""),
# switches/options
descriptor=Track.Descriptor.M3U,
# extra
extra=x
) for x in master.media if x.type == "SUBTITLES"]
)