Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100755 950 lines (763 sloc) 30.961 kb
3728600 @aajanki yle plugin for librtmp
authored
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 """
5 yle-dl - rtmpdump frontend for Yle Areena, Elävä Arkisto and YleX Areena
6
7 Copyright (C) 2010-2012 Antti Ajanki <antti.ajanki@iki.fi>
8
5bc7ded @aajanki Bump version
authored
9 This script extracts RTMP stream information from Yle Areena
ecc41dc @aajanki Update Areena URLs
authored
10 (http://areena.yle.fi), YleX Areena (http://ylex.yle.fi/ylex-areena),
11 Elävä Arkisto (http://yle.fi/elavaarkisto/index.html) web pages and
12 calls rtmpdump with correct parameters.
3728600 @aajanki yle plugin for librtmp
authored
13 """
14
15 import sys
16 import urllib
17 import urllib2
18 import re
19 import subprocess
20 import os
a65b34a @aajanki Implement --destdir
authored
21 import os.path
3728600 @aajanki yle plugin for librtmp
authored
22 import signal
23 import urlparse
24 import htmlentitydefs
25 import json
26 import string
ec8cd78 @aajanki Support Akamai live streams
authored
27 import xml.dom.minidom
e0c268a @aajanki Net radio support
authored
28 import time
76a4b5b @aajanki Append BOM to subtitle files
authored
29 import codecs
479cafe @aajanki Adapt to recent changes in Areena
authored
30 import base64
31 from Crypto.Cipher import AES
3728600 @aajanki yle plugin for librtmp
authored
32
e6d8416 @aajanki Bump version, changelog
authored
33 version = '2.0.1'
3728600 @aajanki yle plugin for librtmp
authored
34
479cafe @aajanki Adapt to recent changes in Areena
authored
35 AREENA_NG_SWF = 'http://areena.yle.fi/static/player/1.2.8/flowplayer/flowplayer.commercial-3.2.7-encrypted.swf'
3728600 @aajanki yle plugin for librtmp
authored
36 AREENA_NG_HTTP_HEADERS = {'User-Agent': 'yle-dl/' + version.split(' ')[0]}
37
38 ARKISTO_SWF = 'http://yle.fi/elavaarkisto/flowplayer/flowplayer.commercial-3.2.7.swf?0.7134730119723827'
39 RTMPDUMP_OPTIONS_ARKISTO = ['-s', ARKISTO_SWF, '-m', '60']
40
41 RTMPDUMP_OPTIONS_YLEX = ['-m', '60']
42
c4e849b @aajanki Better RTMP URL parsing
authored
43 RTMP_SCHEMES = ['rtmp', 'rtmpe', 'rtmps', 'rtmpt', 'rtmpte', 'rtmpts']
44
3728600 @aajanki yle plugin for librtmp
authored
45 # list of all options that require an argument
46 ARGOPTS = ('--rtmp', '-r', '--host', '-n', '--port', '-c', '--socks',
47 '-S', '--swfUrl', '-s', '--tcUrl', '-t', '--pageUrl', '-p',
48 '--app', '-a', '--swfhash', '-w', '--swfsize', '-x', '--swfVfy',
49 '-W', '--swfAge', '-X', '--auth', '-u', '--conn', '-C',
50 '--flashVer', '-f', '--subscribe', '-d', '--flv', '-o',
51 '--timeout', '-m', '--start', '-A', '--stop', '-B', '--token',
52 '-T', '--skip', '-k')
53
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
54 # rtmpdump exit codes
55 RD_SUCCESS = 0
56 RD_FAILED = 1
57 RD_INCOMPLETE = 2
e0c268a @aajanki Net radio support
authored
58
3728600 @aajanki yle plugin for librtmp
authored
59 debug = False
60 excludechars_linux = '*/|'
61 excludechars_windows = '\"*/:<>?|'
62 excludechars = excludechars_linux
63 rtmpdump_binary = None
64
261e740 @aajanki Explicitly encode strings written to stderr
authored
65 def log(msg):
66 enc = sys.stderr.encoding or 'UTF-8'
67 sys.stderr.write(msg.encode(enc, 'backslashreplace'))
68 sys.stderr.write('\n')
b166579 @aajanki flush after printing a message
authored
69 sys.stderr.flush()
261e740 @aajanki Explicitly encode strings written to stderr
authored
70
3728600 @aajanki yle plugin for librtmp
authored
71 def usage():
72 """Print the usage message to stderr"""
261e740 @aajanki Explicitly encode strings written to stderr
authored
73 log(u'yle-dl %s: Download media files from Yle Areena and Elävä Arkisto' % version)
74 log(u'Copyright (C) 2009-2012 Antti Ajanki <antti.ajanki@iki.fi>, license: GPLv2')
75 log(u'')
76 log(u'%s [yle-dl or rtmpdump options] URL' % sys.argv[0])
77 log(u'')
78 log(u'yle-dl options:')
79 log(u'')
80 log(u'--latestepisode Download the latest episode')
81 log(u"--showurl Print librtmp-compatible URL, don't download")
82 log(u'--vfat Create Windows-compatible filenames')
83 log(u'--sublang lang Download subtitles, lang = fin, swe, smi, none or all')
84 log(u'--rtmpdump path Set path to rtmpdump binary')
85 log(u'--destdir dir Save files to dir')
86 log(u'')
87 log(u'rtmpdump options:')
88 log(u'')
3728600 @aajanki yle plugin for librtmp
authored
89 subprocess.call([rtmpdump_binary, '--help'])
90
91 def download_page(url):
92 """Returns contents of a HTML page at url."""
93 if url.find('://') == -1:
94 url = 'http://' + url
95 if '#' in url:
96 url = url[:url.find('#')]
97
98 request = urllib2.Request(url, headers=AREENA_NG_HTTP_HEADERS)
99 try:
100 urlreader = urllib2.urlopen(request)
611c057 @aajanki Get charset from a <meta> tag.
authored
101 content = urlreader.read()
102
3728600 @aajanki yle plugin for librtmp
authored
103 charset = urlreader.info().getparam('charset')
611c057 @aajanki Get charset from a <meta> tag.
authored
104 if not charset:
105 metacharset = re.search(r'<meta [^>]*?charset="(.*?)"', content)
106 if metacharset:
107 charset = metacharset.group(1)
108 if not charset:
3728600 @aajanki yle plugin for librtmp
authored
109 charset = 'iso-8859-1'
110
611c057 @aajanki Get charset from a <meta> tag.
authored
111 return unicode(content, charset, 'replace')
3728600 @aajanki yle plugin for librtmp
authored
112 except urllib2.URLError, exc:
261e740 @aajanki Explicitly encode strings written to stderr
authored
113 log(u"Can't read %s: %s" % (url, exc))
3728600 @aajanki yle plugin for librtmp
authored
114 return None
115 except ValueError:
261e740 @aajanki Explicitly encode strings written to stderr
authored
116 log(u'Invalid URL: ' + url)
3728600 @aajanki yle plugin for librtmp
authored
117 return None
118
119 def encode_url_utf8(url):
120 """Encode the path component of url to percent-encoded UTF8."""
121 (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
122
123 path = path.encode('UTF8')
124
125 # Assume that the path is already encoded if there seems to be
126 # percent encoded entities.
127 if re.search(r'%[0-9A-Fa-f]{2}', path) is None:
128 path = urllib.quote(path, '/+')
129
130 return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
131
132 def decode_html_entity(entity):
133 if not entity:
134 return u''
135
136 try:
137 x = htmlentitydefs.entitydefs[entity]
138 except KeyError:
139 x = entity
140
141 if x.startswith('&#') and x[-1] == ';':
142 x = x[1:-1]
143
144 if x[0] == '#':
145 try:
146 return unichr(int(x[1:]))
147 except (ValueError, OverflowError):
148 return u'?'
149 else:
150 return unicode(x, 'iso-8859-1', 'ignore')
151
152 def replace_entitydefs(content):
153 return re.sub(r'&(.*?);', lambda m: decode_html_entity(m.group(1)), content)
154
155 def sane_filename(name):
156 if isinstance(name, unicode):
157 tr = dict((ord(c), ord(u'_')) for c in excludechars)
158 else:
159 tr = string.maketrans(excludechars, '_'*len(excludechars))
160 x = name.strip(' .').translate(tr)
161 if x:
162 return x
163 else:
164 return 'ylevideo'
165
166 def execute_rtmpdump(args):
167 """Start rtmpdump process with argument list args and wait until
168 completion."""
169 if debug:
261e740 @aajanki Explicitly encode strings written to stderr
authored
170 log('Executing:')
631033a @aajanki Build rtmpdump command line using command line switches.
authored
171 log(' '.join(args))
172
173 enc = sys.getfilesystemencoding()
174 encoded_args = [x.encode(enc, 'replace') for x in args]
3728600 @aajanki yle plugin for librtmp
authored
175
176 try:
631033a @aajanki Build rtmpdump command line using command line switches.
authored
177 rtmpdump_process = subprocess.Popen(encoded_args)
3728600 @aajanki yle plugin for librtmp
authored
178 return rtmpdump_process.wait()
179 except KeyboardInterrupt:
c38f00b @aajanki Catch OSError if the rtmpdump process was already killed
authored
180 try:
181 os.kill(rtmpdump_process.pid, signal.SIGINT)
182 rtmpdump_process.wait()
183 except OSError:
184 # The rtmpdump process died before we killed it.
185 pass
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
186 return RD_INCOMPLETE
3728600 @aajanki yle plugin for librtmp
authored
187 except OSError, exc:
212b851 @aajanki correct encoding in error message
authored
188 log(u'Execution failed: ' + unicode(exc, 'UTF-8', 'replace'))
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
189 return RD_INCOMPLETE
3728600 @aajanki yle plugin for librtmp
authored
190
191 def downloader_factory(url):
192 if url.startswith('http://www.yle.fi/elavaarkisto/') or \
193 url.startswith('http://yle.fi/elavaarkisto/'):
194 return ElavaArkistoDownloader()
195 elif url.startswith('http://ylex.yle.fi/'):
196 return YleXDownloader()
197 else:
198 return AreenaNGDownloader()
199
126495c @aajanki Print output filename before executing rtmpdump
authored
200 def get_output_filename(args_in):
3728600 @aajanki yle plugin for librtmp
authored
201 prev = None
126495c @aajanki Print output filename before executing rtmpdump
authored
202 args = list(args_in) # copy
3728600 @aajanki yle plugin for librtmp
authored
203 while args:
204 opt = args.pop()
205 if opt in ('-o', '--flv'):
206 if prev:
631033a @aajanki Build rtmpdump command line using command line switches.
authored
207 return prev
3728600 @aajanki yle plugin for librtmp
authored
208 else:
209 return None
210 prev = opt
211 return None
212
eb41955 @aajanki Fixed --resume
authored
213 def is_resume_job(args):
214 return '--resume' in args or '-e' in args
215
3728600 @aajanki yle plugin for librtmp
authored
216 def next_available_filename(proposed):
217 i = 1
631033a @aajanki Build rtmpdump command line using command line switches.
authored
218 enc = sys.getfilesystemencoding()
3106582 @aajanki Fix encoding error in next_available_filename()
authored
219 filename = proposed
220 basename, ext = os.path.splitext(filename)
221 while os.path.exists(filename.encode(enc, 'replace')):
261e740 @aajanki Explicitly encode strings written to stderr
authored
222 log(u'%s exists, trying an alternative name' % filename)
3728600 @aajanki yle plugin for librtmp
authored
223 filename = basename + '-' + str(i) + ext
224 i += 1
3106582 @aajanki Fix encoding error in next_available_filename()
authored
225
226 return filename
3728600 @aajanki yle plugin for librtmp
authored
227
228 def which(program):
229 """Search for program on $PATH and return the full path if found."""
230 # Adapted from
231 # http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
232
233 def is_exe(fpath):
234 return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
235
236 fpath, fname = os.path.split(program)
237 if fpath:
238 if is_exe(program):
239 return program
240 else:
241 for path in os.environ["PATH"].split(os.pathsep):
242 exe_file = os.path.join(path, program)
243 if is_exe(exe_file):
244 return exe_file
245
246 return None
247
ba6ae99 @aajanki Support non-Akamai live streams
authored
248 def parse_rtmp_single_component_app(rtmpurl):
249 """Extract single path-component app and playpath from rtmpurl."""
250 # YLE server requires that app is the first path component
251 # only. By default librtmp would take the first two
252 # components (app/appInstance).
253 #
254 # This also means that we can't rely on librtmp's playpath
255 # parser and have to duplicate the logic here.
256 k = 0
257 for i, x in enumerate(rtmpurl):
258 if x == '/':
259 k += 1
260 if k == 4:
261 break
262
263 playpath = rtmpurl[(i+1):]
264 app_only_rtmpurl = rtmpurl[:i]
265
266 ext = os.path.splitext(playpath)[1]
267 if ext == '.mp4':
268 playpath = 'mp4:' + playpath
269 ext = '.flv'
270 elif ext == '.mp3':
271 playpath = 'mp3:' + playpath[:-4]
272
273 return (app_only_rtmpurl, playpath, ext)
274
126495c @aajanki Print output filename before executing rtmpdump
authored
275 def log_output_file(outputfile, done=False):
276 if outputfile and outputfile != '-':
277 if done:
278 log(u'Stream saved to ' + outputfile)
279 else:
280 log(u'Output file: ' + outputfile)
281
3728600 @aajanki yle plugin for librtmp
authored
282
283 ### Areena (new) ###
284
285
286 class AreenaNGDownloader:
479cafe @aajanki Adapt to recent changes in Areena
authored
287 # Extracted from
288 # http://areena.yle.fi/static/player/1.2.8/flowplayer/flowplayer.commercial-3.2.7-encrypted.swf
289 AES_KEY = 'hjsadf89hk123ghk'
290
a65b34a @aajanki Implement --destdir
authored
291 def download_episodes(self, url, parameters, latest_episode, sublang, destdir):
3728600 @aajanki yle plugin for librtmp
authored
292 """Extract all episodes (or just the latest episode if
293 latest_only is True) from url."""
a65b34a @aajanki Implement --destdir
authored
294 return self.process_episodes(url, parameters, latest_episode, sublang, destdir, False)
3728600 @aajanki yle plugin for librtmp
authored
295
296 def print_urls(self, url, latest_episode, sublang='all'):
297 """Extract episodes from url and print their
298 librtmp-compatible URLs on stdout."""
a65b34a @aajanki Implement --destdir
authored
299 return self.process_episodes(url, [], latest_episode, sublang, None, True)
3728600 @aajanki yle plugin for librtmp
authored
300
a65b34a @aajanki Implement --destdir
authored
301 def process_episodes(self, url, parameters, latest_only, sublang, destdir, print_url):
3728600 @aajanki yle plugin for librtmp
authored
302 playlist = self.get_playlist(url, latest_only)
303 if not playlist:
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
304 return RD_FAILED
3728600 @aajanki yle plugin for librtmp
authored
305
306 for clip in playlist:
ee78edb @aajanki Live radio broadcasts
authored
307 res = self.process_single_episode(clip, url, parameters,
308 sublang, destdir, print_url)
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
309 if res != RD_SUCCESS:
3728600 @aajanki yle plugin for librtmp
authored
310 return res
311
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
312 return RD_SUCCESS
3728600 @aajanki yle plugin for librtmp
authored
313
ee78edb @aajanki Live radio broadcasts
authored
314 def process_single_episode(self, clip, pageurl, parameters,
315 sublang, destdir, print_url):
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
316 """Construct clip parameters and starts a rtmpdump process."""
631033a @aajanki Build rtmpdump command line using command line switches.
authored
317 rtmpparams = self.get_rtmp_parameters(clip, pageurl)
318 if not rtmpparams:
1281fbd @aajanki refactor
authored
319 return RD_FAILED
320
321 if print_url:
631033a @aajanki Build rtmpdump command line using command line switches.
authored
322 enc = sys.getfilesystemencoding()
323 print self.rtmp_parameters_to_url(rtmpparams).encode(enc, 'replace')
1281fbd @aajanki refactor
authored
324 return RD_SUCCESS
325
326 outputparam = []
327 if '-o' not in parameters and '--flv' not in parameters:
328 filename = self.get_clip_filename(clip, destdir)
329 if not is_resume_job(parameters):
330 filename = next_available_filename(filename)
631033a @aajanki Build rtmpdump command line using command line switches.
authored
331 outputparam = ['-o', filename]
1281fbd @aajanki refactor
authored
332
333 args = [rtmpdump_binary]
631033a @aajanki Build rtmpdump command line using command line switches.
authored
334 args += self.rtmp_parameters_to_rtmpdump_args(rtmpparams)
1281fbd @aajanki refactor
authored
335 args += outputparam
631033a @aajanki Build rtmpdump command line using command line switches.
authored
336 args += parameters
1281fbd @aajanki refactor
authored
337
338 outputfile = get_output_filename(args)
ee78edb @aajanki Live radio broadcasts
authored
339 media = clip.get('media', {})
1281fbd @aajanki refactor
authored
340 if media.has_key('subtitles'):
341 self.download_subtitles(media['subtitles'], sublang, outputfile)
342
343 log_output_file(outputfile)
344
345 retcode = execute_rtmpdump(args)
346 if retcode != RD_SUCCESS:
347 return retcode
348
349 log_output_file(outputfile, True)
350
351 return retcode
352
631033a @aajanki Build rtmpdump command line using command line switches.
authored
353 def rtmp_parameters_to_url(self, params):
354 components = [params['rtmp']]
355 for key, value in params.iteritems():
356 if key != 'rtmp':
357 components.append('%s=%s' % (key, value))
358 return ' '.join(components)
359
360 def rtmp_parameters_to_rtmpdump_args(self, params):
361 args = []
362 for key, value in params.iteritems():
363 if key == 'live':
364 args.append('--live')
365 else:
366 args.append('--%s=%s' % (key, value))
367 return args
368
1281fbd @aajanki refactor
authored
369 def get_clip_filename(self, clip, destdir):
ee78edb @aajanki Live radio broadcasts
authored
370 if 'channel' in clip:
371 # Live radio broadcast
372 curtime = time.strftime('-%Y-%m-%d-%H:%M:%S')
373 filename = clip['channel'].get('name', 'yle-radio') + curtime + '.flv'
374
375 elif 'title' in clip:
376 # Video or radio stream
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
377 filename = clip['title']
378 date = None
379 broadcasted = clip.get('broadcasted', None)
380 if broadcasted:
381 date = broadcasted.get('date', None)
382 if not date:
383 date = clip.get('published', None)
3728600 @aajanki yle plugin for librtmp
authored
384 if date:
385 filename += '-' + date.replace('/', '-').replace(' ', '-')
386 filename += '.flv'
ee78edb @aajanki Live radio broadcasts
authored
387
3728600 @aajanki yle plugin for librtmp
authored
388 else:
389 filename = 'areena.flv'
390
ee78edb @aajanki Live radio broadcasts
authored
391 filename = sane_filename(filename)
392
a65b34a @aajanki Implement --destdir
authored
393 if destdir:
394 filename = os.path.join(destdir, filename)
1281fbd @aajanki refactor
authored
395
396 return filename
a65b34a @aajanki Implement --destdir
authored
397
631033a @aajanki Build rtmpdump command line using command line switches.
authored
398 def get_rtmp_parameters(self, clip, pageurl):
ee78edb @aajanki Live radio broadcasts
authored
399 if 'channel' in clip:
631033a @aajanki Build rtmpdump command line using command line switches.
authored
400 return self.get_liveradio_rtmp_parameters(clip, pageurl)
ee78edb @aajanki Live radio broadcasts
authored
401 else:
631033a @aajanki Build rtmpdump command line using command line switches.
authored
402 return self.get_tv_rtmp_parameters(clip, pageurl)
ee78edb @aajanki Live radio broadcasts
authored
403
631033a @aajanki Build rtmpdump command line using command line switches.
authored
404 def get_tv_rtmp_parameters(self, clip, pageurl):
ee78edb @aajanki Live radio broadcasts
authored
405 # Search results don't have the media item so we have to
406 # download clip metadata from the source.
407 if not clip.has_key('media'):
408 clip = self.get_metadata(clip)
409 if not clip:
410 return None
411
412 media = clip.get('media', {})
413 if not media:
414 return None
415
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
416 if media.get('live', False) == True:
ee78edb @aajanki Live radio broadcasts
authored
417 islive = True
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
418 papiurl = 'http://papi.yle.fi/ng/live/rtmp/' + media['id'] + '/fin'
3728600 @aajanki yle plugin for librtmp
authored
419 else:
ee78edb @aajanki Live radio broadcasts
authored
420 islive = False
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
421 papiurl = 'http://papi.yle.fi/ng/mod/rtmp/' + media['id']
ee78edb @aajanki Live radio broadcasts
authored
422
631033a @aajanki Build rtmpdump command line using command line switches.
authored
423 return self.rtmp_parameters_from_papi(papiurl, pageurl, islive)
ee78edb @aajanki Live radio broadcasts
authored
424
631033a @aajanki Build rtmpdump command line using command line switches.
authored
425 def get_liveradio_rtmp_parameters(self, clip, pageurl):
ee78edb @aajanki Live radio broadcasts
authored
426 channel = clip.get('channel', {})
427 lang = channel.get('lang', 'fi')
428 radioid = channel.get('id', None)
429 if not radioid:
430 return None
431
432 papiurl = 'http://papi.yle.fi/ng/radio/rtmp/%s/%s' % (radioid, lang)
631033a @aajanki Build rtmpdump command line using command line switches.
authored
433 return self.rtmp_parameters_from_papi(papiurl, pageurl, True)
ee78edb @aajanki Live radio broadcasts
authored
434
631033a @aajanki Build rtmpdump command line using command line switches.
authored
435 def rtmp_parameters_from_papi(self, papiurl, pageurl, islive):
479cafe @aajanki Adapt to recent changes in Areena
authored
436 papi = download_page(papiurl)
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
437 if not papi:
1281fbd @aajanki refactor
authored
438 return None
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
439
479cafe @aajanki Adapt to recent changes in Areena
authored
440 papi_decoded = self.decode_papi(papi)
3728600 @aajanki yle plugin for librtmp
authored
441
570d9b1 @aajanki debug is a global variable
authored
442 if debug:
479cafe @aajanki Adapt to recent changes in Areena
authored
443 log(papi_decoded)
444
445 try:
446 papi_xml = xml.dom.minidom.parseString(papi_decoded)
447 except Exception as exc:
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
448 log(unicode(exc, 'utf-8'))
1281fbd @aajanki refactor
authored
449 return None
479cafe @aajanki Adapt to recent changes in Areena
authored
450
934d6a8 @aajanki make yle-dl more robust
authored
451 nodelist = papi_xml.getElementsByTagName('connect')
452 if len(nodelist) != 1 or len(nodelist[0].childNodes) != 1:
1281fbd @aajanki refactor
authored
453 return None
934d6a8 @aajanki make yle-dl more robust
authored
454 rtmp_connect = nodelist[0].firstChild.nodeValue
479cafe @aajanki Adapt to recent changes in Areena
authored
455
934d6a8 @aajanki make yle-dl more robust
authored
456 nodelist = papi_xml.getElementsByTagName('stream')
457 if len(nodelist) != 1 or len(nodelist[0].childNodes) != 1:
1281fbd @aajanki refactor
authored
458 return None
934d6a8 @aajanki make yle-dl more robust
authored
459 rtmp_stream = nodelist[0].firstChild.nodeValue
479cafe @aajanki Adapt to recent changes in Areena
authored
460
c4e849b @aajanki Better RTMP URL parsing
authored
461 try:
462 scheme, edgefcs, rtmppath = self.rtmpurlparse(rtmp_connect)
463 except ValueError:
464 return None
9ac1afe @aajanki Workaround for OS X urlparse issue
authored
465
479cafe @aajanki Adapt to recent changes in Areena
authored
466 ident = download_page('http://%s/fcs/ident' % edgefcs)
467 if ident is None:
1281fbd @aajanki refactor
authored
468 return None
479cafe @aajanki Adapt to recent changes in Areena
authored
469
470 if debug:
471 log(ident)
472
473 try:
474 identxml = xml.dom.minidom.parseString(ident)
475 except Exception as exc:
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
476 log(unicode(exc, 'utf-8'))
1281fbd @aajanki refactor
authored
477 return None
479cafe @aajanki Adapt to recent changes in Areena
authored
478
934d6a8 @aajanki make yle-dl more robust
authored
479 nodelist = identxml.getElementsByTagName('ip')
480 if len(nodelist) != 1 or len(nodelist[0].childNodes) != 1:
1281fbd @aajanki refactor
authored
481 return None
934d6a8 @aajanki make yle-dl more robust
authored
482 rtmp_ip = nodelist[0].firstChild.nodeValue
483
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
484 app_without_fcsvhost = rtmppath.lstrip('/')
485 app_fields = app_without_fcsvhost.split('?', 1)
486 baseapp = app_fields[0]
487 if len(app_fields) > 1:
488 auth = app_fields[1]
489 else:
490 auth = ''
934d6a8 @aajanki make yle-dl more robust
authored
491 app = '%s?_fcs_vhost=%s&%s' % (baseapp, edgefcs, auth)
c4e849b @aajanki Better RTMP URL parsing
authored
492 rtmpbase = '%s://%s/%s' % (scheme, edgefcs, baseapp)
ee78edb @aajanki Live radio broadcasts
authored
493 tcurl = '%s://%s/%s' % (scheme, rtmp_ip, app)
479cafe @aajanki Adapt to recent changes in Areena
authored
494
631033a @aajanki Build rtmpdump command line using command line switches.
authored
495 rtmpparams = {'rtmp': rtmpbase,
496 'app': app,
497 'playpath': rtmp_stream,
498 'tcUrl': tcurl,
499 'pageUrl': pageurl,
500 'swfUrl': AREENA_NG_SWF}
ee78edb @aajanki Live radio broadcasts
authored
501 if islive:
631033a @aajanki Build rtmpdump command line using command line switches.
authored
502 rtmpparams['live'] = '1'
261e740 @aajanki Explicitly encode strings written to stderr
authored
503
631033a @aajanki Build rtmpdump command line using command line switches.
authored
504 return rtmpparams
3728600 @aajanki yle plugin for librtmp
authored
505
c4e849b @aajanki Better RTMP URL parsing
authored
506 def rtmpurlparse(self, url):
507 if '://' not in url:
508 raise ValueError("Invalid RTMP URL")
509
510 scheme, rest = url.split('://', 1)
511 if scheme not in RTMP_SCHEMES:
512 raise ValueError("Invalid RTMP URL")
513
514 if '/' not in rest:
515 raise ValueError("Invalid RTMP URL")
516
517 server, app_and_playpath = rest.split('/', 1)
518 return (scheme, server, app_and_playpath)
519
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
520 def get_metadata(self, clip):
521 jsonurl = self.create_pageurl(clip) + '.json'
522 jsonstr = download_page(jsonurl)
523 if not jsonstr:
524 return None
525
526 try:
527 clipjson = json.loads(jsonstr)
528 except ValueError:
529 log(u'Invalid JSON file at ' + jsonurl)
530 return None
531
532 return clipjson
533
479cafe @aajanki Adapt to recent changes in Areena
authored
534 def decode_papi(self, papi):
535 try:
536 bytestring = base64.b64decode(str(papi))
537 except (UnicodeEncodeError, TypeError):
538 return None
539
540 iv = bytestring[:16]
541 ciphertext = bytestring[16:]
542 padlen = 16 - (len(ciphertext) % 16)
543 ciphertext = ciphertext + '\0'*padlen
544
545 decrypter = AES.new(self.AES_KEY, AES.MODE_CFB, iv, segment_size=16*8)
546 return decrypter.decrypt(ciphertext)[:-padlen]
547
3728600 @aajanki yle plugin for librtmp
authored
548 def get_playlist(self, url, latest_episode):
ee78edb @aajanki Live radio broadcasts
authored
549 if url == 'http://yle.fi/puhe/live':
550 # Radio Puhe uses non-standard metadta location, hardcode
551 # the values here.
552 return [{'channel': {'lang': 'fi',
553 'name': 'Yle Radio Puhe',
554 'id': '48'}}]
555
3728600 @aajanki yle plugin for librtmp
authored
556 (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
6494eb1 @aajanki program page json has changed
authored
557 episodeurl = urlparse.urlunparse((scheme, netloc, path + '.json', params, query, ''))
558 jsonstr = download_page(episodeurl)
ee78edb @aajanki Live radio broadcasts
authored
559 if not jsonstr:
560 return None
3728600 @aajanki yle plugin for librtmp
authored
561
6494eb1 @aajanki program page json has changed
authored
562 if debug:
563 log(jsonstr)
3728600 @aajanki yle plugin for librtmp
authored
564
6494eb1 @aajanki program page json has changed
authored
565 try:
566 fulldata = json.loads(jsonstr)
567 except ValueError:
568 log(u'Invalid JSON file at ' + episodeurl)
569 return None
3728600 @aajanki yle plugin for librtmp
authored
570
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
571 playlist = []
ee78edb @aajanki Live radio broadcasts
authored
572 if 'contentType' in fulldata or 'channel' in fulldata:
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
573 playlist = [fulldata]
574 elif 'search' in fulldata:
575 playlist = fulldata['search'].get('results', [])
971cfc9 @aajanki Get clips too
authored
576 elif 'availableEpisodes' in fulldata or \
577 'availableClips' in fulldata:
578 available_episodes = fulldata.get('availableEpisodes', {})
579 available_clips = fulldata.get('availableClips', {})
580 playlist = available_episodes.get('video', []) + \
581 available_episodes.get('audio', []) + \
582 available_clips.get('video', []) + \
583 available_clips.get('audio', [])
3728600 @aajanki yle plugin for librtmp
authored
584
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
585 if latest_episode:
844f26f @aajanki Sort according to broadcast time when downloading --latestepisode
authored
586 playlist = sorted(playlist, key=self.get_media_time)[-1:]
3728600 @aajanki yle plugin for librtmp
authored
587
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
588 return playlist
3728600 @aajanki yle plugin for librtmp
authored
589
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
590 def create_pageurl(self, media):
591 if 'type' not in media or 'id' not in media:
592 return ''
3728600 @aajanki yle plugin for librtmp
authored
593
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
594 if media['type'] == 'audio':
595 urltype = 'radio'
3728600 @aajanki yle plugin for librtmp
authored
596 else:
bdf722c @aajanki Get clip parameters from JSON instead of HTML page
authored
597 urltype = 'tv'
598
599 return 'http://areena.yle.fi/%s/%s' % (urltype, media['id'])
651c5b8 @aajanki better JS parsing by Jouni Seppänen
authored
600
3728600 @aajanki yle plugin for librtmp
authored
601 def download_subtitles(self, available_subtitles, preferred_lang, videofilename):
602 basename = os.path.splitext(videofilename)[0]
603 subtitlefile = None
604 for sub in available_subtitles:
605 lang = sub.get('lang', '')
606 if lang == preferred_lang or preferred_lang == 'all':
607 url = sub.get('url', None)
608 if url:
609 try:
610 subtitlefile = basename + '.' + lang + '.srt'
631033a @aajanki Build rtmpdump command line using command line switches.
authored
611 enc = sys.getfilesystemencoding()
612 urllib.urlretrieve(url, subtitlefile.encode(enc, 'replace'))
76a4b5b @aajanki Append BOM to subtitle files
authored
613 self.add_BOM(subtitlefile)
261e740 @aajanki Explicitly encode strings written to stderr
authored
614 log(u'Subtitles saved to ' + subtitlefile)
3728600 @aajanki yle plugin for librtmp
authored
615 if preferred_lang != 'all':
616 return subtitlefile
617 except IOError, exc:
261e740 @aajanki Explicitly encode strings written to stderr
authored
618 log(u'Failed to download subtitles at %s: %s' % (url, exc))
3728600 @aajanki yle plugin for librtmp
authored
619 return subtitlefile
620
76a4b5b @aajanki Append BOM to subtitle files
authored
621 def add_BOM(self, filename):
622 """Add byte-order mark into a file.
623
624 Assumes (but does not check!) that the file is UTF-8 encoded.
625 """
631033a @aajanki Build rtmpdump command line using command line switches.
authored
626 enc = sys.getfilesystemencoding()
627 encoded_filename = filename.encode(enc, 'replace')
628 content = open(encoded_filename, 'r').read()
76a4b5b @aajanki Append BOM to subtitle files
authored
629 if content.startswith(codecs.BOM_UTF8):
630 return
631
631033a @aajanki Build rtmpdump command line using command line switches.
authored
632 f = open(encoded_filename, 'w')
76a4b5b @aajanki Append BOM to subtitle files
authored
633 f.write(codecs.BOM_UTF8)
634 f.write(content)
635 f.close()
636
844f26f @aajanki Sort according to broadcast time when downloading --latestepisode
authored
637 def parse_yle_date(self, yledate):
638 """Convert strings like 2012-06-16T18:45:00 into a struct_time.
639
640 Returns None if parsing fails.
641 """
642 try:
643 return time.strptime(yledate, '%Y-%m-%dT%H:%M:%S')
644 except (ValueError, TypeError):
645 return None
646
647 def get_media_time(self, media):
648 """Extract date (as struct_time) from media metadata."""
649 broadcasted = media.get('broadcasted', {}) or {}
650 return self.parse_yle_date(broadcasted.get('date', None)) or \
651 self.parse_yle_date(media.get('published', None)) or \
652 time.gmtime(0)
653
3728600 @aajanki yle plugin for librtmp
authored
654
655 ### Elava Arkisto ###
656
657
658 class ElavaArkistoDownloader:
659
660 def extract_playlist(self, mediajson):
661 pagedata = json.loads(mediajson)
662 if not pagedata.has_key('media'):
663 return []
664
665 clips = []
666 for mediaitem in pagedata['media']:
667 title = sane_filename(mediaitem.get('title', 'elavaarkisto'))
668
669 downloadURL = mediaitem.get('downloadURL', None)
670
671 bestrate = 0
672 bestrtmpurl = ''
673 for clip in mediaitem.get('urls', {}).get('domestic', []):
674 rate = float(clip.get('bitrate', 0))
675 url = clip.get('url', '')
676 if rate > bestrate and url:
677 bestrate = rate
678 bestrtmpurl = url
679
680 if not bestrtmpurl:
681 continue
682
ba6ae99 @aajanki Support non-Akamai live streams
authored
683 rtmpurl, playpath, ext = parse_rtmp_single_component_app(bestrtmpurl)
684
685 clips.append({'rtmp': rtmpurl,
3728600 @aajanki yle plugin for librtmp
authored
686 'playpath': playpath,
687 'downloadURL': downloadURL,
688 'filename': title + ext})
689
690 return clips
691
692 def download_single_episode(self, rtmpurl, playpath, downloadURL,
693 filename, parameters, pageurl):
694 if downloadURL:
261e740 @aajanki Explicitly encode strings written to stderr
authored
695 log('Downloading from HTTP server...')
126495c @aajanki Print output filename before executing rtmpdump
authored
696 log_output_file(filename)
697
631033a @aajanki Build rtmpdump command line using command line switches.
authored
698 enc = sys.getfilesystemencoding()
3728600 @aajanki yle plugin for librtmp
authored
699 try:
700 urllib.urlretrieve(downloadURL, filename.encode(enc))
701 except IOError, exc:
261e740 @aajanki Explicitly encode strings written to stderr
authored
702 log(u'Download failed: ' + str(exc))
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
703 return RD_FAILED
126495c @aajanki Print output filename before executing rtmpdump
authored
704
705 log_output_file(filename, True)
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
706 return RD_SUCCESS
3728600 @aajanki yle plugin for librtmp
authored
707 else:
708 args = [rtmpdump_binary]
709 args += RTMPDUMP_OPTIONS_ARKISTO
631033a @aajanki Build rtmpdump command line using command line switches.
authored
710 args += ['-r', rtmpurl,
711 '-y', playpath,
712 '-p', pageurl,
713 '-o', filename]
3728600 @aajanki yle plugin for librtmp
authored
714 args += parameters
715
126495c @aajanki Print output filename before executing rtmpdump
authored
716 outputfile = get_output_filename(args)
717 log_output_file(outputfile)
718
3728600 @aajanki yle plugin for librtmp
authored
719 retcode = execute_rtmpdump(args)
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
720 if retcode != RD_SUCCESS:
3728600 @aajanki yle plugin for librtmp
authored
721 return retcode
722
126495c @aajanki Print output filename before executing rtmpdump
authored
723 log_output_file(outputfile, True)
3728600 @aajanki yle plugin for librtmp
authored
724
725 return retcode
726
727 def print_librtmp_url(self, rtmpurl, playpath, pageurl, downloadURL):
728 """Print a librtmp-compatible Elava Arkisto URL to stdout."""
729 if downloadURL:
730 print downloadURL
731 else:
732 print '%s playpath=%s swfUrl=%s pageUrl=%s' % \
733 (rtmpurl, playpath, ARKISTO_SWF, pageurl)
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
734 return RD_SUCCESS
3728600 @aajanki yle plugin for librtmp
authored
735
736 def get_playlist(self, url, latest_episode):
737 (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
738
739 if '.' in path:
740 path = path.rsplit('.', 1)[0]
741 path = path + '.json'
742 jsonurl = urlparse.urlunparse((scheme, netloc, path, '', '', ''))
743
744 mediajson = download_page(jsonurl)
745 if mediajson is None:
746 return None
747
748 # Yle server sends UTF-8 but doesn't set charset in
749 # Content-type header. This will workaround the problem.
750 mediajson = mediajson.encode('iso-8859-1').decode('utf-8')
751
752 playlist = self.extract_playlist(mediajson)
753 if len(playlist) == 0:
261e740 @aajanki Explicitly encode strings written to stderr
authored
754 log(u"Can't find streams at %s." % url)
3728600 @aajanki yle plugin for librtmp
authored
755 return None
756
757 if latest_episode:
758 playlist = playlist[:1]
759
760 return playlist
761
a65b34a @aajanki Implement --destdir
authored
762 def download_episodes(self, url, parameters, latest_episode, sublang, destdir):
3728600 @aajanki yle plugin for librtmp
authored
763 """Download playlist from Elava Arkisto page at url and
764 download all clips using rtmpdump."""
765 playlist = self.get_playlist(url, latest_episode)
766 if playlist is None:
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
767 return RD_FAILED
3728600 @aajanki yle plugin for librtmp
authored
768
769 for clip in playlist:
a65b34a @aajanki Implement --destdir
authored
770 filename = clip['filename']
771 if destdir:
772 filename = os.path.join(destdir, filename)
773
eb41955 @aajanki Fixed --resume
authored
774 if not is_resume_job(parameters):
775 filename = next_available_filename(filename)
776
3728600 @aajanki yle plugin for librtmp
authored
777 status = self.download_single_episode(clip['rtmp'],
778 clip['playpath'],
779 clip['downloadURL'],
a65b34a @aajanki Implement --destdir
authored
780 filename,
3728600 @aajanki yle plugin for librtmp
authored
781 parameters, url)
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
782 if status != RD_SUCCESS:
3728600 @aajanki yle plugin for librtmp
authored
783 return status
784
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
785 return RD_SUCCESS
3728600 @aajanki yle plugin for librtmp
authored
786
787 def print_urls(self, url, latest_episode):
788 """Download playlist from Elava Arkisto page at url and print
789 a librtmp-compatible URL for each clip."""
790 playlist = self.get_playlist(url, latest_episode)
791 if playlist is None:
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
792 return RD_FAILED
3728600 @aajanki yle plugin for librtmp
authored
793
794 for clip in playlist:
795 self.print_librtmp_url(clip['rtmp'], clip['playpath'],
796 url, clip['downloadURL'])
797
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
798 return RD_SUCCESS
3728600 @aajanki yle plugin for librtmp
authored
799
800
801 ### YleX Areena ###
802
803
03ee109 @aajanki YleX has switched to the new interface, too
authored
804 class YleXDownloader(AreenaNGDownloader):
a65b34a @aajanki Implement --destdir
authored
805 def download_episodes(self, url, argv, latest_episode, sublang, destdir):
3728600 @aajanki yle plugin for librtmp
authored
806 """Download a stream from the given YleX Areena url using
807 rtmpdump."""
808 html = download_page(url)
809 if not html:
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
810 return RD_FAILED
3728600 @aajanki yle plugin for librtmp
authored
811
812 outputoptions = []
813 if not '-o' in argv and not '--flv' in argv:
814 match = re.search(r'<h1[^>]*>(.*?)</h1>', html)
815 if match:
816 filename = sane_filename(replace_entitydefs(match.group(1))) + '.flv'
817 else:
818 filename = 'ylex.flv'
a65b34a @aajanki Implement --destdir
authored
819
820 if destdir:
ec8cd78 @aajanki Support Akamai live streams
authored
821 filename = os.path.join(destdir, filename)
a65b34a @aajanki Implement --destdir
authored
822
eb41955 @aajanki Fixed --resume
authored
823 if not is_resume_job(argv):
824 filename = next_available_filename(filename)
825
631033a @aajanki Build rtmpdump command line using command line switches.
authored
826 outputoptions = ['-o', filename]
3728600 @aajanki yle plugin for librtmp
authored
827
631033a @aajanki Build rtmpdump command line using command line switches.
authored
828 rtmpparams = self.get_rtmp_parameters(html, url)
829 if not rtmpparams:
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
830 return RD_FAILED
3728600 @aajanki yle plugin for librtmp
authored
831
832 args = [rtmpdump_binary]
631033a @aajanki Build rtmpdump command line using command line switches.
authored
833 args += self.rtmp_parameters_to_rtmpdump_args(rtmpparams)
3728600 @aajanki yle plugin for librtmp
authored
834 args += RTMPDUMP_OPTIONS_YLEX
835 args += outputoptions
836 args += argv
837
126495c @aajanki Print output filename before executing rtmpdump
authored
838 outputfile = get_output_filename(args)
839 log_output_file(outputfile)
840
3728600 @aajanki yle plugin for librtmp
authored
841 retcode = execute_rtmpdump(args)
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
842 if retcode != RD_SUCCESS:
3728600 @aajanki yle plugin for librtmp
authored
843 return retcode
844
126495c @aajanki Print output filename before executing rtmpdump
authored
845 log_output_file(outputfile, True)
3728600 @aajanki yle plugin for librtmp
authored
846
847 return retcode
848
849 def print_urls(self, url, latest_episode):
850 """Print a librtmp-compatible YleX Areena URL to stdout."""
851 html = download_page(url)
852 if not html:
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
853 return RD_FAILED
3728600 @aajanki yle plugin for librtmp
authored
854
631033a @aajanki Build rtmpdump command line using command line switches.
authored
855 rtmpparams = self.get_rtmp_parameters(html, url)
856 if not rtmpparams:
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
857 return RD_FAILED
3728600 @aajanki yle plugin for librtmp
authored
858
631033a @aajanki Build rtmpdump command line using command line switches.
authored
859 enc = sys.getfilesystemencoding()
860 print self.rtmp_parameters_to_url(rtmpparams).encode(enc, 'replace')
7806784 @aajanki Use RD_SUCCESS/RD_FAILED status codes everywhere
authored
861 return RD_SUCCESS
3728600 @aajanki yle plugin for librtmp
authored
862
631033a @aajanki Build rtmpdump command line using command line switches.
authored
863 def get_rtmp_parameters(self, html, pageurl):
03ee109 @aajanki YleX has switched to the new interface, too
authored
864 match = re.search(r'<meta +?property="og:image" +?content="(.+?)" *?/>', html)
3728600 @aajanki yle plugin for librtmp
authored
865 if not match:
866 return None
867
03ee109 @aajanki YleX has switched to the new interface, too
authored
868 match = re.search(r'/([a-fA-F0-9]+)_', match.group(1))
869 if not match:
3728600 @aajanki yle plugin for librtmp
authored
870 return None
871
03ee109 @aajanki YleX has switched to the new interface, too
authored
872 papiurl = 'http://papi.yle.fi/ng/mod/rtmp/%s' % match.group(1)
631033a @aajanki Build rtmpdump command line using command line switches.
authored
873 return self.rtmp_parameters_from_papi(papiurl, pageurl, False)
03ee109 @aajanki YleX has switched to the new interface, too
authored
874
3728600 @aajanki yle plugin for librtmp
authored
875
876 ### main program ###
877
878
879 def main():
880 global debug
881 global rtmpdump_binary
882 latest_episode = False
883 url_only = False
884 sublang = 'all'
885 show_usage = False
886 url = None
a65b34a @aajanki Implement --destdir
authored
887 destdir = None
3728600 @aajanki yle plugin for librtmp
authored
888
e831660 @aajanki Convert command line arguments into unicode
authored
889 #argv = list(sys.argv[1:])
890
891 # Is sys.getfilesystemencoding() the correct encoding for
892 # sys.argv?
893 encoding = sys.getfilesystemencoding()
894 argv = [unicode(x, encoding, 'ignore') for x in sys.argv[1:]]
3728600 @aajanki yle plugin for librtmp
authored
895 rtmpdumpargs = []
896 while argv:
897 arg = argv.pop(0)
898 if not arg.startswith('-'):
899 url = arg
900 elif arg in ['--verbose', '-V', '--debug', '-z']:
901 debug = True
902 rtmpdumpargs.append(arg)
903 elif arg in ['--help', '-h']:
904 show_usage = True
905 elif arg in ['--latestepisode']:
906 latest_episode = True
907 elif arg == '--showurl':
908 url_only = True
909 elif arg == '--vfat':
910 global excludechars
911 global excludechars_windows
912 excludechars = excludechars_windows
913 elif arg == '--sublang':
914 if argv:
915 sublang = argv.pop(0)
916 elif arg == '--rtmpdump':
917 if argv:
918 rtmpdump_binary = argv.pop(0)
a65b34a @aajanki Implement --destdir
authored
919 elif arg == '--destdir':
920 if argv:
921 destdir = argv.pop(0)
3728600 @aajanki yle plugin for librtmp
authored
922 else:
923 rtmpdumpargs.append(arg)
924 if arg in ARGOPTS and argv:
925 rtmpdumpargs.append(argv.pop(0))
926
927 if not rtmpdump_binary:
928 if sys.platform == 'win32':
929 rtmpdump_binary = which('rtmpdump.exe')
930 else:
931 rtmpdump_binary = which('rtmpdump')
932 if not rtmpdump_binary:
261e740 @aajanki Explicitly encode strings written to stderr
authored
933 log(u'Error: rtmpdump not found on path, use --rtmpdump for setting the location')
3728600 @aajanki yle plugin for librtmp
authored
934
935 if show_usage or url is None:
936 usage()
937 sys.exit(1)
938
939 url = encode_url_utf8(url)
940 dl = downloader_factory(url)
941
942 if url_only:
943 sys.exit(dl.print_urls(url, latest_episode))
944 else:
a65b34a @aajanki Implement --destdir
authored
945 sys.exit(dl.download_episodes(url, rtmpdumpargs, latest_episode, sublang, destdir))
3728600 @aajanki yle plugin for librtmp
authored
946
947
948 if __name__ == '__main__':
949 main()
Something went wrong with that request. Please try again.