Last active 1450529936

jtvrtmp.py Raw
1#!/usr/bin/env python2
2
3# rtmpdump parameter generator for justin.tv/twitch.tv streams v6
4# * Usage: jtvrtmp.py channelname [quality]
5# where channelname is the channel name ([twitch/justin].tv/channelname)
6# and quality is an optional parameter with a valid quality setting
7# (360p, 480p, 720p, etc) If quality isn't present, it selects 'live'.
8# * if quality is 'live', it picks the quality setting with a +, which
9# is a restream of the video being sent to jtv, no transcoding involved.
10#
11# Changelog:
12# v6: Added documentation!
13# v5: Changed to default to 'live' quality instead of 360p, as some streams
14# don't have 360p transcoding; made the code in main() slightly cleaner,
15# possibly shorter.
16# v4: Add shebang line
17# v3: Removed unicode copyright symbol
18# v2: Added exception catching for when a stream is offline or the specified
19# quality setting is unavailable.
20# v1: Initial release
21#
22# Copyright 2013 Steven Smith (blha303). All Rights Reserved.
23# New BSD license
24# http://www.opensource.org/licenses/BSD-3-Clause
25
26from urllib2 import urlopen
27import json
28from sys import argv, exit, stderr
29import traceback
30
31def getswfaddr(channelname):
32 """ Follows the redirect to get the channel player url, strips out the useragent """
33 return urlopen("http://www.justin.tv/widgets/live_embed_player.swf?channel=" + channelname).geturl().split("&userAgent")[0]
34
35def getstreaminfo(channelname):
36 """ returns a dict of {quality: data}. Example output in streaminfooutput.txt """
37 data = json.loads(urlopen("http://usher.justin.tv/find/%s.json?type=any" % channelname.lower()).read())
38 newd = {}
39 for i in data:
40 newd[i['display']] = i
41 return newd
42
43def buildcmdline(channelname, data=None, quality="live"):
44 """ Builds and returns rtmpdump parameter string for loading a twitch/justin.tv channel.
45 Usage: buildcmdline(channelname as string, data dict from usher.justin.tv, formatted as {quality: data}
46 Check getstreaminfo() for how to create this dict, or let buildcmdline call it by not setting data. """
47 if not data:
48 data = getstreaminfo(channelname)
49 if quality == "live":
50 for i in data:
51 if "Source" in i:
52 quality = i
53 try:
54 data = data[quality]
55 except KeyError:
56 return '-q; echo "-------------"; echo "Couldn\'t find stream info for %s, maybe the stream is offline?"; echo "-------------" #' % quality
57 try:
58 if not "live-cdn" in data["connect"] and not "justintvlivefs" in data["connect"]:
59 justinlegacyparams = '-j "%s" ' % data["token"].replace('"', r'\"')
60 else:
61 justinlegacyparams = ""
62 except KeyError:
63 qualities = []
64 for i in data.keys():
65 stderr.write(str(i))
66 stderr.write(str(data))
67 if "connect" in data[i]:
68 qualities.append(i)
69 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
70 out = '-r "%s/%s" %s--swfVfy "%s" -v -o -' % (data["connect"], data["play"], justinlegacyparams, getswfaddr(channelname))
71 return out
72
73def main(args):
74 """ Returns rtmpdump parameter string for channelname in specified list;
75 argv is a list, either [channelname] or [channelname, quality] """
76 if len(args) < 2:
77 return "Usage: %s channelname [quality]" % args[0]
78 elif len(args) > 2:
79 return buildcmdline(args[1], quality=args[2])
80 else:
81 return buildcmdline(args[1])
82
83if __name__ == "__main__":
84 try:
85 out = main(argv)
86 print out
87 if "Usage:" in out:
88 exit(2)
89 except:
90 print '-q; echo "-------------"; echo "Error:"#'
91 traceback.print_exc()
jtvrtmp.sh Raw
1#!/bin/bash
2# Quality setting in this file doesn't work at the moment. I'm open to suggestions.
3# Copyright 2013 Steven Smith (blha303). All Rights Reserved.
4# New BSD license
5# http://www.opensource.org/licenses/BSD-3-Clause
6
7die () {
8 echo >&2 "$@"
9 exit 1
10}
11
12(( $# == 1 )) || die "Usage: jtvrtmp.sh channelname [quality]"
13(( $# == 2 )) || echo "rtmpdump $(python jtvrtmp.py $1) | vlc -" | sh; exit
14(( $# == 3 )) || echo "rtmpdump $(python jtvrtmp.py $1 $2) | vlc -" | sh; exit
15
streaminfooutput.txt Raw
1# Output for python -c "import jtvrtmp; print jtvrtmp.getstreaminfo('Ludendi')" 2013-07-22
2{u'360p':
3 {u'node': u'video8-2.lax01',
4 u'needed_info': u'',
5 u'play': u'jtv_SAz4Mud24V7aKSw0',
6 u'meta_game': u'The Legend of Zelda: Ocarina of Time',
7 u'cluster': u'lax01',
8 u'type': u'360p',
9 u'broadcast_part': 1,
10 u'persistent': u'true',
11 u'video_height': 360,
12 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"}',
13 u'connect': u'rtmp://199.9.254.164/app',
14 u'broadcast_id': 6230376768L,
15 u'bitrate': 512,
16 u'display': u'360p',
17 u'find_type': u'dist'
18 },
19u'480p':
20 {u'node': u'video2-2.lax01',
21 u'needed_info': u'',
22 u'play': u'jtv_yv5WRBFg0ftTWUEO',
23 u'meta_game': u'The Legend of Zelda: Ocarina of Time',
24 u'cluster': u'lax01',
25 u'type': u'480p',
26 u'broadcast_part': 1,
27 u'persistent': u'true',
28 u'video_height': 480,
29 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"}',
30 u'connect': u'rtmp://199.9.254.158/app',
31 u'broadcast_id': 6230376752L,
32 u'bitrate': 768,
33 u'display': u'480p',
34 u'find_type': u'dist'
35 },
36u'720p+':
37 {u'node': u'video6-1.lax01',
38 u'needed_info': u'',
39 u'play': u'jtv_fGEW0H_C_0Dfy2bI',
40 u'meta_game': u'The Legend of Zelda: Ocarina of Time',
41 u'cluster': u'lax01',
42 u'type': u'live',
43 u'broadcast_part': 6,
44 u'persistent': u'true',
45 u'video_height': 720,
46 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"}',
47 u'connect': u'rtmp://199.9.254.138/app',
48 u'broadcast_id': 6230375632L,
49 u'bitrate': 723.296875,
50 u'display': u'720p+',
51 u'find_type': u'dist'
52 }
53}