This commit is contained in:
Nirvana
2026-01-29 19:02:31 +01:00
parent e433344a40
commit c84bffffc3
2 changed files with 97 additions and 0 deletions
+27
View File
@@ -214,6 +214,33 @@ def setup_m3u_routes(app, manager, service):
response.status = 500
return {"error": f"Internal server error: {str(api_err)}"}
@app.route("/api/providers/<provider>/m3u/decrypted/ffmpeg")
def get_m3u_decrypted_ffmpeg_provider(provider):
"""
Generates decrypted M3U playlist for a specific provider with ffmpeg piping.
Fast generation - includes ALL channels with ffmpeg-piped decrypted stream URLs.
Streams are highest quality video only with all audio tracks copied.
No filtering, no caching (very fast).
Example: http://localhost:7777/api/providers/rtlplus/m3u/decrypted/ffmpeg
"""
try:
logger.info(f"Generating fast decrypted ffmpeg M3U playlist for provider '{provider}'")
return service._generate_m3u_decrypted_ffmpeg_fast(providers=provider)
except ValueError as val_err:
logger.error(
f"API Error in /api/providers/{provider}/m3u/decrypted/ffmpeg: {str(val_err)}"
)
response.status = 404
return {"error": str(val_err)}
except Exception as api_err:
logger.error(
f"API Error in /api/providers/{provider}/m3u/decrypted/ffmpeg: {str(api_err)}"
)
response.status = 500
return {"error": f"Internal server error: {str(api_err)}"}
@app.route("/api/providers/<provider>/m3u/decrypted/filtered")
def get_m3u_decrypted_filtered_provider(provider):
"""
+70
View File
@@ -7,6 +7,7 @@ import time
from urllib.parse import parse_qsl, urlencode
from bottle import Bottle, redirect, request, response, run
from typing import Optional
# Add lib path for imports
script_dir = os.path.dirname(os.path.abspath(__file__))
@@ -671,6 +672,75 @@ class UltimateService:
return m3u_content
def _generate_m3u_decrypted_ffmpeg_fast(self, providers: Optional[str] = None) -> str:
"""
Generate M3U playlist with ffmpeg-piped decrypted streams (highest quality video only).
Similar to _generate_m3u_decrypted_fast but wraps streams in ffmpeg pipe commands.
Args:
providers: Optional comma-separated list of provider names to filter
Returns:
M3U playlist content as string
"""
lines = ["#EXTM3U"]
# Parse providers filter
provider_filter = None
if providers:
provider_filter = [p.strip() for p in providers.split(",")]
# Get all channels from all providers
all_channels = []
for provider_name in self.manager.list_providers():
if provider_filter and provider_name not in provider_filter:
continue
try:
channels = self.manager.list_channels(provider_name)
for channel in channels:
all_channels.append((provider_name, channel))
except Exception as e:
logger.warning(f"Failed to list channels for provider {provider_name}: {e}")
continue
# Generate M3U entries for each channel
for provider_name, channel in all_channels:
try:
# Get channel metadata
channel_id = channel.get("id")
channel_name = channel.get("name", channel_id)
# Get base URL for the stream
base_url = request.url.rsplit("/", 4)[0] # Remove /api/providers/<provider>/m3u/decrypted/ffmpeg
stream_url = f"{base_url}/api/providers/{provider_name}/channels/{channel_id}/stream/decrypted/ffmpeg/index.mpd"
# Build ffmpeg pipe command
# Map all video (will be just one due to highest_quality_only), all audio, and optional subtitles
ffmpeg_cmd = (
f'pipe://ffmpeg -loglevel fatal '
f'-i "{stream_url}" '
f'-map 0:v -map 0:a -map 0:s? ' # Map video, all audio, optional subtitles
f'-c:v copy -c:a copy ' # Copy all codecs
f'-f mpegts ' # MPEG-TS output format
f'-metadata service_name="{channel_name}" ' # Set service name
f'pipe:1'
)
# Add M3U entry
lines.append(f'#EXTINF:-1,{channel_name}')
lines.append(ffmpeg_cmd)
except Exception as e:
logger.warning(
f"Failed to generate ffmpeg M3U entry for {provider_name}/{channel.get('id')}: {e}"
)
continue
playlist = "\n".join(lines)
response.content_type = "audio/x-mpegurl; charset=utf-8"
return playlist
def _generate_m3u_decrypted_filtered_content(
self, providers=None, save_to_cache=True, cache_filename=None
):