Last active
December 19, 2015 22:58
-
-
Save blha303/6031124 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python2 | |
# rtmpdump parameter generator for justin.tv/twitch.tv streams v6 | |
# * Usage: jtvrtmp.py channelname [quality] | |
# where channelname is the channel name ([twitch/justin].tv/channelname) | |
# and quality is an optional parameter with a valid quality setting | |
# (360p, 480p, 720p, etc) If quality isn't present, it selects 'live'. | |
# * if quality is 'live', it picks the quality setting with a +, which | |
# is a restream of the video being sent to jtv, no transcoding involved. | |
# | |
# Changelog: | |
# v6: Added documentation! | |
# v5: Changed to default to 'live' quality instead of 360p, as some streams | |
# don't have 360p transcoding; made the code in main() slightly cleaner, | |
# possibly shorter. | |
# v4: Add shebang line | |
# v3: Removed unicode copyright symbol | |
# v2: Added exception catching for when a stream is offline or the specified | |
# quality setting is unavailable. | |
# v1: Initial release | |
# | |
# Copyright 2013 Steven Smith (blha303). All Rights Reserved. | |
# New BSD license | |
# http://www.opensource.org/licenses/BSD-3-Clause | |
from urllib2 import urlopen | |
import json | |
from sys import argv, exit, stderr | |
import traceback | |
def getswfaddr(channelname): | |
""" Follows the redirect to get the channel player url, strips out the useragent """ | |
return urlopen("http://www.justin.tv/widgets/live_embed_player.swf?channel=" + channelname).geturl().split("&userAgent")[0] | |
def getstreaminfo(channelname): | |
""" returns a dict of {quality: data}. Example output in streaminfooutput.txt """ | |
data = json.loads(urlopen("http://usher.justin.tv/find/%s.json?type=any" % channelname.lower()).read()) | |
newd = {} | |
for i in data: | |
newd[i['display']] = i | |
return newd | |
def buildcmdline(channelname, data=None, quality="live"): | |
""" Builds and returns rtmpdump parameter string for loading a twitch/justin.tv channel. | |
Usage: buildcmdline(channelname as string, data dict from usher.justin.tv, formatted as {quality: data} | |
Check getstreaminfo() for how to create this dict, or let buildcmdline call it by not setting data. """ | |
if not data: | |
data = getstreaminfo(channelname) | |
if quality == "live": | |
for i in data: | |
if "Source" in i: | |
quality = i | |
try: | |
data = data[quality] | |
except KeyError: | |
return '-q; echo "-------------"; echo "Couldn\'t find stream info for %s, maybe the stream is offline?"; echo "-------------" #' % quality | |
try: | |
if not "live-cdn" in data["connect"] and not "justintvlivefs" in data["connect"]: | |
justinlegacyparams = '-j "%s" ' % data["token"].replace('"', r'\"') | |
else: | |
justinlegacyparams = "" | |
except KeyError: | |
qualities = [] | |
for i in data.keys(): | |
stderr.write(str(i)) | |
stderr.write(str(data)) | |
if "connect" in data[i]: | |
qualities.append(i) | |
return '-q; echo "-------------"; echo "Couldn\'t find stream info for %s, maybe you need to be subscribed to watch that resolution? Try one of these: ' + ", ".join(qualities) + '"; echo "-------------" #' % quality | |
out = '-r "%s/%s" %s--swfVfy "%s" -v -o -' % (data["connect"], data["play"], justinlegacyparams, getswfaddr(channelname)) | |
return out | |
def main(args): | |
""" Returns rtmpdump parameter string for channelname in specified list; | |
argv is a list, either [channelname] or [channelname, quality] """ | |
if len(args) < 2: | |
return "Usage: %s channelname [quality]" % args[0] | |
elif len(args) > 2: | |
return buildcmdline(args[1], quality=args[2]) | |
else: | |
return buildcmdline(args[1]) | |
if __name__ == "__main__": | |
try: | |
out = main(argv) | |
print out | |
if "Usage:" in out: | |
exit(2) | |
except: | |
print '-q; echo "-------------"; echo "Error:"#' | |
traceback.print_exc() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Quality setting in this file doesn't work at the moment. I'm open to suggestions. | |
# Copyright 2013 Steven Smith (blha303). All Rights Reserved. | |
# New BSD license | |
# http://www.opensource.org/licenses/BSD-3-Clause | |
die () { | |
echo >&2 "$@" | |
exit 1 | |
} | |
(( $# == 1 )) || die "Usage: jtvrtmp.sh channelname [quality]" | |
(( $# == 2 )) || echo "rtmpdump $(python jtvrtmp.py $1) | vlc -" | sh; exit | |
(( $# == 3 )) || echo "rtmpdump $(python jtvrtmp.py $1 $2) | vlc -" | sh; exit |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Output for python -c "import jtvrtmp; print jtvrtmp.getstreaminfo('Ludendi')" 2013-07-22 | |
{u'360p': | |
{u'node': u'video8-2.lax01', | |
u'needed_info': u'', | |
u'play': u'jtv_SAz4Mud24V7aKSw0', | |
u'meta_game': u'The Legend of Zelda: Ocarina of Time', | |
u'cluster': u'lax01', | |
u'type': u'360p', | |
u'broadcast_part': 1, | |
u'persistent': u'true', | |
u'video_height': 360, | |
u'token': u'26b8cee84453fd5e370d0ba13108a1ef59c9e58b:{"swfDomains": ["justin.tv", "jtvx.com", "xarth.com","twitchtv.com", "twitch.tv","newjtv.com", "jtvnw.net", "wdtinc.com", "imapweather.com", "facebook.com", "starcrafting.com"], "streamName": "jtv_SAz4Mud24V7aKSw0", "expiration": 1374431364, "geo_ip": "124.169.218.124", "server": "video8-2.lax01"}', | |
u'connect': u'rtmp://199.9.254.164/app', | |
u'broadcast_id': 6230376768L, | |
u'bitrate': 512, | |
u'display': u'360p', | |
u'find_type': u'dist' | |
}, | |
u'480p': | |
{u'node': u'video2-2.lax01', | |
u'needed_info': u'', | |
u'play': u'jtv_yv5WRBFg0ftTWUEO', | |
u'meta_game': u'The Legend of Zelda: Ocarina of Time', | |
u'cluster': u'lax01', | |
u'type': u'480p', | |
u'broadcast_part': 1, | |
u'persistent': u'true', | |
u'video_height': 480, | |
u'token': u'5e7a6465c72d71b455d0d78249ecf50e8cb64462:{"swfDomains": ["justin.tv", "jtvx.com", "xarth.com", "twitchtv.com", "twitch.tv", "newjtv.com", "jtvnw.net", "wdtinc.com", "imapweather.com", "facebook.com", "starcrafting.com"], "streamName": "jtv_yv5WRBFg0ftTWUEO", "expiration": 1374431364, "geo_ip": "124.169.218.124", "server": "video2-2.lax01"}', | |
u'connect': u'rtmp://199.9.254.158/app', | |
u'broadcast_id': 6230376752L, | |
u'bitrate': 768, | |
u'display': u'480p', | |
u'find_type': u'dist' | |
}, | |
u'720p+': | |
{u'node': u'video6-1.lax01', | |
u'needed_info': u'', | |
u'play': u'jtv_fGEW0H_C_0Dfy2bI', | |
u'meta_game': u'The Legend of Zelda: Ocarina of Time', | |
u'cluster': u'lax01', | |
u'type': u'live', | |
u'broadcast_part': 6, | |
u'persistent': u'true', | |
u'video_height': 720, | |
u'token': u'88830ed1f5bab63eb512e01d45698106ecb500b1:{"swfDomains": ["justin.tv", "jtvx.com", "xarth.com", "twitchtv.com", "twitch.tv", "newjtv.com", "jtvnw.net", "wdtinc.com", "imapweather.com", "facebook.com", "starcrafting.com"], "streamName": "jtv_fGEW0H_C_0Dfy2bI", "expiration": 1374431364, "geo_ip": "124.169.218.124", "server": "video6-1.lax01"}', | |
u'connect': u'rtmp://199.9.254.138/app', | |
u'broadcast_id': 6230375632L, | |
u'bitrate': 723.296875, | |
u'display': u'720p+', | |
u'find_type': u'dist' | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment