Fix for M3U8 Hotstar
This commit is contained in:
parent
5b88624051
commit
a8edede94b
@ -71,7 +71,7 @@ class Track:
|
||||
ISM = 4 # https://bitmovin.com/blog/microsoft-smooth-streaming-mss/
|
||||
|
||||
def __init__(self, id_, source, url, codec, language=None, descriptor=Descriptor.URL,
|
||||
needs_proxy=False, needs_repack=False, encrypted=False, psshWV=None, psshPR=None, note=None, kid=None, key=None, extra=None):
|
||||
needs_proxy=False, needs_repack=False, encrypted=False, psshWV=None, psshPR=None, note=None, kid=None, key=None, extra=None, original_url=None):
|
||||
self.id = id_
|
||||
self.source = source
|
||||
self.url = url
|
||||
@ -94,6 +94,8 @@ class Track:
|
||||
# extra data
|
||||
self.extra = extra or {} # allow anything for extra, but default to a dict
|
||||
|
||||
self.original_url = original_url
|
||||
|
||||
# should only be set internally
|
||||
self._location = None
|
||||
|
||||
|
||||
@ -196,6 +196,7 @@ def parse(*, url=None, data=None, source, session=None, downloader=None):
|
||||
tracks.append(VideoTrack(
|
||||
id_=track_id,
|
||||
source=source,
|
||||
original_url=url,
|
||||
url=url,
|
||||
# metadata
|
||||
codec=(codec or "").split(".")[0],
|
||||
|
||||
@ -8,115 +8,118 @@ 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.
|
||||
"""
|
||||
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.
|
||||
: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`.
|
||||
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.
|
||||
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...")
|
||||
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"]
|
||||
psshPR = keys[0].split(",")[-1] if keys else None
|
||||
# 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"]
|
||||
psshPR = keys[0].split(",")[-1] if keys else None
|
||||
|
||||
|
||||
widevine_keys = [x.uri for x in master.session_keys if x.keyformat.lower() == "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"]
|
||||
psshWV = widevine_keys[0].split(",")[-1] if widevine_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
|
||||
# )))
|
||||
|
||||
widevine_keys = [x.uri for x in master.session_keys if x.keyformat.lower() == "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"]
|
||||
psshWV = widevine_keys[0].split(",")[-1] if widevine_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"),
|
||||
hlg=False, # TODO: Can we get this from the manifest.xml?
|
||||
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),
|
||||
psshWV=psshWV,
|
||||
psshPR=psshPR,
|
||||
# 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
|
||||
psshWV=psshWV,
|
||||
psshPR=psshPR,
|
||||
# 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"]
|
||||
)
|
||||
return Tracks(
|
||||
# VIDEO
|
||||
[VideoTrack(
|
||||
id_=md5(str(x).encode()).hexdigest()[0:7], # 7 chars only for filename length
|
||||
source=source,
|
||||
original_url=x.base_uri + x.uri,
|
||||
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"),
|
||||
hlg=False, # TODO: Can we get this from the manifest.xml?
|
||||
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),
|
||||
psshWV=psshWV,
|
||||
psshPR=psshPR,
|
||||
# extra
|
||||
extra=x
|
||||
) for x in master.playlists],
|
||||
# AUDIO
|
||||
[AudioTrack(
|
||||
id_=md5(str(x).encode()).hexdigest()[0:6],
|
||||
source=source,
|
||||
original_url=x.base_uri + x.uri,
|
||||
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
|
||||
psshWV=psshWV,
|
||||
psshPR=psshPR,
|
||||
# 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,
|
||||
original_url=x.base_uri + x.uri,
|
||||
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"]
|
||||
)
|
||||
|
||||
@ -287,7 +287,8 @@ def parse(*, url=None, data=None, source, session=None, downloader=None):
|
||||
tracks.append(VideoTrack(
|
||||
id_=track_id,
|
||||
source=source,
|
||||
url=url if source == "HS" else track_url,
|
||||
original_url=url,
|
||||
url=track_url,
|
||||
# metadata
|
||||
codec=(codecs or "").split(".")[0],
|
||||
language=track_lang,
|
||||
@ -324,7 +325,8 @@ def parse(*, url=None, data=None, source, session=None, downloader=None):
|
||||
tracks.append(AudioTrack(
|
||||
id_=track_id,
|
||||
source=source,
|
||||
url=url if source == "HS" else track_url,
|
||||
original_url=url,
|
||||
url=track_url,
|
||||
# metadata
|
||||
codec=(codecs or "").split(".")[0],
|
||||
language=track_lang,
|
||||
@ -375,6 +377,7 @@ def parse(*, url=None, data=None, source, session=None, downloader=None):
|
||||
tracks.append(TextTrack(
|
||||
id_=track_id,
|
||||
source=source,
|
||||
original_url=url,
|
||||
url=track_url,
|
||||
# metadata
|
||||
codec=(codecs or "").split(".")[0],
|
||||
@ -390,6 +393,7 @@ def parse(*, url=None, data=None, source, session=None, downloader=None):
|
||||
tracks.append(TextTrack(
|
||||
id_=track_id,
|
||||
source=source,
|
||||
original_url=url,
|
||||
url=track_url,
|
||||
# metadata
|
||||
codec=(codecs or "").split(".")[0],
|
||||
|
||||
@ -248,8 +248,7 @@ async def m3u8dl(uri, out, track, headers=None, proxy=None):
|
||||
ffmpeg_binary = shutil.which("ffmpeg") or "/usr/bin/ffmpeg"
|
||||
arguments = [
|
||||
executable,
|
||||
uri,
|
||||
**["--max-speed", "12M"] if "akamai" in uri else "",
|
||||
track.original_url or uri,
|
||||
"--save-dir", f'"{os.path.dirname(out)}"',
|
||||
"--tmp-dir", f'"{os.path.dirname(out)}"',
|
||||
"--save-name", f'"{os.path.basename(out).replace(".mp4", "")}"',
|
||||
@ -263,14 +262,18 @@ async def m3u8dl(uri, out, track, headers=None, proxy=None):
|
||||
if headers and track.source == "HS":
|
||||
arguments.extend(["--header", f'"Cookie:{headers["cookie"].replace(" ", "")}"'])
|
||||
#for k,v in headers.items():
|
||||
|
||||
|
||||
if "akamai" in uri:
|
||||
arguments.append("--max-speed")
|
||||
arguments.append("12M")
|
||||
|
||||
if proxy:
|
||||
arguments.extend(["--custom-proxy", proxy])
|
||||
if not ("linux" in platform):
|
||||
arguments.extend(["--http-request-timeout", "8"])
|
||||
if track.__class__.__name__ == "VideoTrack":
|
||||
if track.height:
|
||||
from vinetrimmer.objects.tracks import Track
|
||||
if track.height and not (track.descriptor == Track.Descriptor.M3U):
|
||||
arguments.extend([
|
||||
"-sv", f"res='{track.height}*':codec='{track.codec}':for=best"
|
||||
])
|
||||
|
||||
Loading…
Reference in New Issue
Block a user