Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Ways to re-stream and decrypt MPEG-CENC live streams. (or just play on desktop)

Disclaimer

  1. All content keys were redacted, they should be 128-bit hex strings.
  2. These methods involve the use of ffmpeg and -cenc_decryption_key which is not part of stable releases as of July 2022. Use nightlies from gyan.dev or some other autobuild in the meantime. Hopefully it gets added in the next stable release.
  3. On my end none of these were particularly reliable. This may change in the future as FFmpeg evolves. The direct method of playing with ffplay is currently the most reliable out of the listed methods.

xTeVe (for Emby/Plex)

Using the ffdecrypt MiTM stub as xTeVe's ffmpeg binary in settings works fairly well for Plex/Emby. Once its stable and buffers enough not to have to re-buffer, it tends to stay stable. The only issues I've noticed are when the ffmpeg call fails for x/y/z reason. E.g., when it times out getting a segment or similar HTTP related wonkyness. The issue is its not particularly asynchronous so ffmpeg failing or timing out will delay and freeze the entire stream quite abruptly.

When the stream slows down below the live playback rate and causes the player client (xTeVe/emby/plex) to buffer, it causes a great deal of snowballing issues that on my end eventually lead to playback death needing to be ended fully, wait for xTeVe cache to clear, and then begin again.

Overall if it doesnt error out, it works fairly well.

#!C:\Python310\python.exe
import subprocess
import sys
# This is a python script that can be used as a fake `ffmpeg` binary.
# Remove `.py` from the file extension and change the shebang above to point to your Python installation.
# Then pass this as the ffmpeg binary to any script or software that can take an ffmpeg binary for various use.
# If the program doesnt honor the shebang or complains, try with a .cmd or .bat file with:
# @echo off
# "C:\Python310\python.exe" "%~dp0\ffdecrypt" %*
# and make sure that file is saved right next to the `ffdecrypt` file.
# Doing this has the benefit of being able to MiTM the arguments for modification or injection of new args.
# Its currently setup to do similar re-stream + cenc decrypt as the other methods shown here.
# The code related to audio, video variables (mappings) may not be wanted for your use-case but I recommend them
# in most cases. For example xTeVe will choose the first-found track of each track type and use exclusively that.
# So to get around that we map the only track of each track type as the best tracks, then xTeVe has no choice but
# to use the best tracks.
DRM_CONTENT_KEYS = {
"rte": {
"channel1": "REDACTED",
"channel2": "REDACTED",
"channel3": "REDACTED",
"channel4": "REDACTED"
},
"channel4": {
"c4": "REDACTED",
"e4": "REDACTED",
"m4": "REDACTED",
"f4": "REDACTED",
"4s": "REDACTED"
},
"channel5": {
"channel5": "REDACTED",
"5usa": "REDACTED"
},
"itv": {
"itv1": "REDACTED",
"itv2": "REDACTED",
"itv3": "REDACTED",
"itv4": "REDACTED",
"itvbe": "REDACTED",
"citv": "REDACTED"
}
}
ffmpeg_args = sys.argv[1:]
url_index = ffmpeg_args.index("-i") + 1
url = ffmpeg_args[url_index]
service, channel = None, None
video, audio = None, None
if "c4ukdash-eb.tls1.yospace.com" in url:
# e.g., https://csm-e-c4ukdash-eb.tls1.yospace.com/csm/extlive/channelfour01,c4-v2-iso-dash-h12.mpd
service = "channel4"
channel = url.split("channelfour01,")[1].split("-")[0]
video, audio = 6, 1
elif "live.rte.ie" in url:
# e.g., https://live.rte.ie/live/a/channel1/channel1.isml/.mpd
service = "rte"
channel = url.split("/")[-2].split(".isml")[0]
video, audio = 6, 0
elif "simadotcomitv.com" in url or "simamobile.itv.com":
# e.g., https://itv1simamobile.itv.com/playout/mb01/itv1/cenc.isml/.mpd?... (or itv1simadotcom.itv.com and such)
service = "itv"
channel = url.split("/")[2].split("sima")[0]
video, audio = 5, 0
elif url.startswith("https://akadashlive-c5", "https://akadashlive-5usa", "https://akadashlive-5star", "https://akadashlive-paramount", "https://akadashlive-5select"):
# e.g., https://akadashlive-c5.akamaized.net/out/v1/07646f4532504e45a2a8647f59372d1c/index.mpd
service = "channel5"
channel = url.split(".")[0].split("-")[-1]
video, audio = 4, 0
if audio is not None:
ffmpeg_args.insert(url_index + 1, "-map")
ffmpeg_args.insert(url_index + 2, f"0:a:{audio}")
if video is not None:
ffmpeg_args.insert(url_index + 1, "-map")
ffmpeg_args.insert(url_index + 2, f"0:v:{video}")
if service and channel:
content_key = DRM_CONTENT_KEYS[service][channel]
ffmpeg_args = ["-re", "-cenc_decryption_key", content_key] + ffmpeg_args
subprocess.Popen(["ffmpeg.exe", *ffmpeg_args])
# -sn was incredibly important on my end, possibly relating to embedded Closed Captions (though it also has WebVTT subtitles).
# without -sn, it would play 1 frame of video and 1 sample of audio before freezing forever.
.\ffplay.exe `
-cenc_decryption_key REDACTED `
-vst v:6 -ast a:1 -sn `
"https://csm-e-c4ukdash-eb.tls1.yospace.com/csm/extlive/channelfour01,e4-v2-iso-dash-h12.mpd?yo.ac=false&yo.br=false"
# best to run this in a subfolder as it makes a few m3u8 files and some extra folders containing a lot of ts files.
# -re is important, i've had weird stutter and looping issues without it.
# the -b params are actually specifying the original bitrate as that information is missing.
.\ffmpeg.exe `
-re `
-timeout 5 `
-cenc_decryption_key REDACTED `
-i "https://csm-e-c4ukdash-eb.tls1.yospace.com/csm/extlive/channelfour01,e4-v2-iso-dash-h12.mpd?yo.ac=false&yo.br=false" `
-c copy `
-http_persistent 1 `
-map 0:v:6 -map 0:a:1 `
-b:v:0 1500k -b:a:0 128k `
-f hls `
-hls_init_time 2 `
-hls_time 2 `
-hls_list_size 5 `
-hls_delete_threshold 5 `
-hls_segment_filename "%v/%Y%m%dT%H%M%S-%%04d.ts" `
-strftime 1 `
-strftime_mkdir 1 `
-hls_flags delete_segments+second_level_segment_index `
-var_stream_map "v:0 a:0" `
-master_pl_name "master.m3u8" `
%v.m3u8
# hacky ffmpeg->mpv method, a direct mpv method doesn't seem to be possible.
# this is effectively the same as the udp method but piped to mpv instead of through udp.
# you could also just use udp, then `mpv udp://host:port` and it will play.
Invoke-NativeCommand -FilePath ".\ffmpeg" -ArgumentList @(
"-hide_banner", "-loglevel", "panic",
"-re",
"-cenc_decryption_key", "REDACTED",
"-i", "https://csm-e-c4ukdash-eb.tls1.yospace.com/csm/extlive/channelfour01,e4-v2-iso-dash-h12.mpd?yo.ac=false&yo.br=true",
"-c", "copy",
"-map", "0:6", "-map", "0:8",
"-f", "mpegts",
"pipe:"
) |
Invoke-NativeCommand -FilePath "mpv" -ArgumentList @("-") |
Receive-RawPipeline
# -re is important, i've had weird stutter and looping issues without it.
.\ffmpeg.exe `
-re `
-timeout 5 `
-cenc_decryption_key REDACTED `
-i "https://csm-e-c4ukdash-eb.tls1.yospace.com/csm/extlive/channelfour01,e4-v2-iso-dash-h12.mpd?yo.ac=false&yo.br=false" `
-c copy `
-http_persistent 1 `
-map 0:6 -map 0:8 `
-f mpegts `
udp://127.0.0.1:8003
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment