public
Last active

  • Download Gist
jtvrtmp.py
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
#!/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()
jtvrtmp.sh
Shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#!/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
streaminfooutput.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
# 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'
}
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.