Skip ffmpeg for extensionless HLS segments
This commit is contained in:
+43
-7
@@ -247,6 +247,14 @@ def resolve_hls_input_url(stream):
|
||||
return best_hls_variant_url(url, data)
|
||||
|
||||
|
||||
def hls_media_playlist(stream, input_url):
|
||||
playlist = fetch_text(input_url, headers=stream.get("headers") or {})
|
||||
if "#EXT-X-STREAM-INF" in playlist:
|
||||
input_url = best_hls_variant_url(input_url, playlist)
|
||||
playlist = fetch_text(input_url, headers=stream.get("headers") or {})
|
||||
return input_url, playlist
|
||||
|
||||
|
||||
def ffmpeg_base_command(stream, input_url):
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
@@ -320,6 +328,17 @@ def parse_hls_segments(playlist_url, playlist_text):
|
||||
return segments
|
||||
|
||||
|
||||
def hls_segments_need_native_download(segments):
|
||||
for segment in segments or []:
|
||||
path = urlparse(segment.get("url") or "").path
|
||||
name = path.rsplit("/", 1)[-1]
|
||||
if "." not in name:
|
||||
return True
|
||||
if "/ad-site-i18n/" in path:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def decrypt_aes128_segment(data, key, iv_value):
|
||||
openssl = shutil.which("openssl")
|
||||
if not openssl:
|
||||
@@ -343,10 +362,7 @@ def decrypt_aes128_segment(data, key, iv_value):
|
||||
|
||||
|
||||
def download_hls_segments(stream, input_url, target, partial):
|
||||
playlist = fetch_text(input_url, headers=stream.get("headers") or {})
|
||||
if "#EXT-X-STREAM-INF" in playlist:
|
||||
input_url = best_hls_variant_url(input_url, playlist)
|
||||
playlist = fetch_text(input_url, headers=stream.get("headers") or {})
|
||||
input_url, playlist = hls_media_playlist(stream, input_url)
|
||||
segments = parse_hls_segments(input_url, playlist)
|
||||
if not segments:
|
||||
return 1
|
||||
@@ -357,7 +373,7 @@ def download_hls_segments(stream, input_url, target, partial):
|
||||
segment_dir.mkdir(parents=True, exist_ok=True)
|
||||
key_cache = {}
|
||||
try:
|
||||
print(f"Direct ffmpeg HLS failed; downloading {len(segments)} playlist segments...")
|
||||
print(f"Downloading {len(segments)} HLS playlist segments directly...")
|
||||
with ts_file.open("wb") as joined:
|
||||
for index, segment in enumerate(segments, start=1):
|
||||
data = fetch_bytes(segment["url"], headers=stream.get("headers") or {}, timeout=60)
|
||||
@@ -392,7 +408,7 @@ def download_hls_segments(stream, input_url, target, partial):
|
||||
"mp4",
|
||||
str(partial),
|
||||
]
|
||||
code = subprocess.call(cmd)
|
||||
code = run_ffmpeg(cmd, timeout=300)
|
||||
if code == 0 and partial.exists() and partial.stat().st_size > 0:
|
||||
partial.replace(target)
|
||||
return 0
|
||||
@@ -405,6 +421,14 @@ def download_hls_segments(stream, input_url, target, partial):
|
||||
shutil.rmtree(segment_dir, ignore_errors=True)
|
||||
|
||||
|
||||
def run_ffmpeg(cmd, timeout=180):
|
||||
try:
|
||||
return subprocess.run(cmd, check=False, timeout=timeout).returncode
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f"ffmpeg timed out after {timeout}s; trying fallback.", file=sys.stderr)
|
||||
return 124
|
||||
|
||||
|
||||
def download_subtitles(subtitles, output_base):
|
||||
saved = []
|
||||
english = [
|
||||
@@ -430,6 +454,18 @@ def download_episode(stream, target):
|
||||
path.unlink()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
if stream.get("isM3U8") or ".m3u8" in str(input_url):
|
||||
try:
|
||||
media_url, playlist = hls_media_playlist(stream, input_url)
|
||||
segments = parse_hls_segments(media_url, playlist)
|
||||
if hls_segments_need_native_download(segments):
|
||||
print("HLS playlist uses extensionless provider segments; skipping direct ffmpeg.")
|
||||
code = download_hls_segments(stream, media_url, target, partial)
|
||||
if code == 0:
|
||||
return 0
|
||||
return code
|
||||
except Exception as exc:
|
||||
print(f"Native HLS preflight failed: {exc}", file=sys.stderr)
|
||||
attempts = [
|
||||
[
|
||||
*ffmpeg_base_command(stream, input_url),
|
||||
@@ -468,7 +504,7 @@ def download_episode(stream, target):
|
||||
partial.unlink()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
code = subprocess.call(cmd)
|
||||
code = run_ffmpeg(cmd)
|
||||
if code == 0 and partial.exists() and partial.stat().st_size > 0:
|
||||
partial.replace(target)
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user