Skip to content

Instantly share code, notes, and snippets.

@rlaphoenix
Last active April 7, 2024 16:19
Show Gist options
  • Star 23 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save rlaphoenix/855422bd562d4dfb2e0a0ac277959bb6 to your computer and use it in GitHub Desktop.
Save rlaphoenix/855422bd562d4dfb2e0a0ac277959bb6 to your computer and use it in GitHub Desktop.
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
@rlaphoenix
Copy link
Author

Hi! Thanks for this

1. I am trying to do the above thing on Linux and getting error `Failed to set value 'KEY' for option 'cenc_decryption_key': Option not found` I think it's probably because it might have to be custom build? Can you please provide any reference for Linux?

2. Make FFMPEG select best Audio/Video Quality, Removing `-map`  arg seems to work for me

3. Video + Audio is too stuttered sometimes the video gets super fast or sometimes it freezes, Most issue is with Audio.

Would be very great if you can help with all the above doubt!

  1. You need FFMPEG nightly, its not on stable.
  2. It will work, its not necessary, but this means its loading more tracks that necessary especially at the start when it trick plays.
  3. Yes its weird, not sure why.

@Neo-1977
Copy link

Neo-1977 commented Oct 4, 2022

I would advise looking into how wvtohls handles this using ffmpeg/nginx/php. It handles this greatly for most channels for live streaming without stutters or issues. Lacks on the error logging but it might give you ideas on how to automate it in another language. Let me know if you want it sent over

@okidokios
Copy link

I would advise looking into how wvtohls handles this using ffmpeg/nginx/php. It handles this greatly for most channels for live streaming without stutters or issues. Lacks on the error logging but it might give you ideas on how to automate it in another language. Let me know if you want it sent over

Can you share wvtohls? probably ffmpeg on wvtohls just restreams the segments with python, but have to check

@Neo-1977
Copy link

Neo-1977 commented Oct 9, 2022

I would advise looking into how wvtohls handles this using ffmpeg/nginx/php. It handles this greatly for most channels for live streaming without stutters or issues. Lacks on the error logging but it might give you ideas on how to automate it in another language. Let me know if you want it sent over

Can you share wvtohls? probably ffmpeg on wvtohls just restreams the segments with python, but have to check

It uses PHP/ffmpeg/Nginx compiled together. There is no python code inside it that does it. I will share on my page shortly.
There is also a Nodejs variation

@okidokios
Copy link

I would advise looking into how wvtohls handles this using ffmpeg/nginx/php. It handles this greatly for most channels for live streaming without stutters or issues. Lacks on the error logging but it might give you ideas on how to automate it in another language. Let me know if you want it sent over

Can you share wvtohls? probably ffmpeg on wvtohls just restreams the segments with python, but have to check

It uses PHP/ffmpeg/Nginx compiled together. There is no python code inside it that does it. I will share on my page shortly. There is also a Nodejs variation

Oh cool will be great if you can share it! so we can take a look how it works!

@Neo-1977
Copy link

I would advise looking into how wvtohls handles this using ffmpeg/nginx/php. It handles this greatly for most channels for live streaming without stutters or issues. Lacks on the error logging but it might give you ideas on how to automate it in another language. Let me know if you want it sent over

Can you share wvtohls? probably ffmpeg on wvtohls just restreams the segments with python, but have to check

It uses PHP/ffmpeg/Nginx compiled together. There is no python code inside it that does it. I will share on my page shortly. There is also a Nodejs variation

Oh cool will be great if you can share it! so we can take a look how it works!

Check my Repo! Nodejs up now. Php iteration to drop later

@LiamKenyon
Copy link

I would advise looking into how wvtohls handles this using ffmpeg/nginx/php. It handles this greatly for most channels for live streaming without stutters or issues. Lacks on the error logging but it might give you ideas on how to automate it in another language. Let me know if you want it sent over

Can you share wvtohls? probably ffmpeg on wvtohls just restreams the segments with python, but have to check

It uses PHP/ffmpeg/Nginx compiled together. There is no python code inside it that does it. I will share on my page shortly. There is also a Nodejs variation

Oh cool will be great if you can share it! so we can take a look how it works!

Check my Repo! Nodejs up now. Php iteration to drop later

Did you ever drop php version?

@Neo-1977
Copy link

No, it seems others would rather share it on github & encrypt the source.

If you wish me to release let me know

@LiamKenyon
Copy link

No, it seems others would rather share it on github & encrypt the source.

If you wish me to release let me know

Would be appreciated as prefer to look at untouched source code than encrypted versions.

@lem6ns
Copy link

lem6ns commented Mar 28, 2023

for MPV, you can pass the following argument:
--demuxer-lavf-o=cenc_decryption_key=REDACTED

@linkinstreetGit
Copy link

linkinstreetGit commented Jun 18, 2023

Thanks for your code. It works, but I think the source MPD file that I am trying to play is not really coded properly, so FFMPEG keeps trying to find segments that have yet to exists and returning 404. And this is even with the -re flag. FFPLAY also have this issue, where it would play one segment, and then just drops everything as it tries to find the next segment which has yet to exist.

I wonder if there are any way to slow down ffmpeg's seeking

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment