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