Skip to content

Instantly share code, notes, and snippets.

@samicrusader
Created August 19, 2023 06:19
Show Gist options
  • Save samicrusader/09d212119705624be14c8269eebf8065 to your computer and use it in GitHub Desktop.
Save samicrusader/09d212119705624be14c8269eebf8065 to your computer and use it in GitHub Desktop.
videos.sproutvideo.com/embed/ downloader
# needs PyAV BeautifulSoup4 and PyCryptoDome
import av
import base64
import json
import requests
from bs4 import BeautifulSoup
from Crypto.Cipher import AES
from io import BytesIO
def constructLink(dat: dict, type: str, bp: str, path: str):
return f'https://{dat["base"]}.videos.sproutvideo.com/{dat["s3_user_hash"]}/{dat["s3_video_hash"]}/{bp}/{path}?Policy={dat["signatures"][type]["CloudFront-Policy"]}&Signature={dat["signatures"][type]["CloudFront-Signature"]}&Key-Pair-Id={dat["signatures"][type]["CloudFront-Key-Pair-Id"]}&sessionID={dat["sessionID"]}'
link = '<feed me a videos.sproutvideo.com embed link>'
html = requests.get(link).text
soup = BeautifulSoup(html, 'lxml')
dat = soup.find('script').text
dat = dat.split('= \'')[1].strip('\';')
dat = base64.b64decode(dat).decode()
dat = json.loads(dat)
indexm3u8link = constructLink(dat, 'm', 'video', 'index.m3u8')
print('main m3u8 index link:', indexm3u8link)
indexm3u8 = requests.get(indexm3u8link).text
indexm3u8 = indexm3u8.replace('#EXTM3U\n', '')
indexm3u8 = indexm3u8.split('\n')
for i in zip(indexm3u8[0::2], indexm3u8[1::2]):
res = i[0].split('RESOLUTION=')[1].split(', ')[0]
link = constructLink(dat, 'm', 'video', i[1])
print(f'{res} m3u8 link: {link}')
m3u8chunks = requests.get(link).text
aeskeypath = constructLink(dat, 'k', 'video', m3u8chunks.split('#EXT-X-KEY:METHOD=AES-128,URI="')[1].split('",')[0])
aeskey = requests.get(aeskeypath).content
aesiv = m3u8chunks.split('IV=')[1].split('\n')[0].lstrip('0x')
aesiv = bytes.fromhex(aesiv)
print(aeskey, aesiv)
aesobj = AES.new(key=aeskey, mode=AES.MODE_CBC, iv=aesiv)
m3u8segments = m3u8chunks.split('#EXTINF:10.000000,\n', 1)[1].split('\n')
print(m3u8segments)
output_container = av.open(dat['title'], 'w')
video_stream = None
audio_stream = None
for i in zip(m3u8segments[0::2], m3u8segments[1::2]):
if i[0].strip().endswith('.ts'):
print('grabbing chunk', i[0].strip())
chunk = requests.get(constructLink(dat, 't', 'video', i[0].strip())).content
decrypted = aesobj.decrypt(chunk)
input_container = av.open(BytesIO(decrypted))
input_video = input_container.streams.get(video=0)[0]
input_audio = input_container.streams.get(audio=0)[0]
if not video_stream:
video_stream = output_container.add_stream(template=input_video)
if not audio_stream:
audio_stream = output_container.add_stream(template=input_audio)
for i in [(input_video, video_stream), (input_audio, audio_stream)]:
for packet in input_container.demux(i[0]):
if packet.dts is None:
continue
packet.stream = i[1]
output_container.mux(packet)
output_container.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment