aboutsummaryrefslogtreecommitdiffstats
path: root/youtube_dl/downloader/rtmp.py
blob: b165e396f5f4499bf67874254132b4facdbc78aa (plain)
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
import os
import re
import subprocess
import sys
import time

from .common import FileDownloader
from ..utils import (
    encodeFilename,
    format_bytes,
)


class RtmpFD(FileDownloader):
    def real_download(self, filename, info_dict):
        def run_rtmpdump(args):
            start = time.time()
            resume_percent = None
            resume_downloaded_data_len = None
            proc = subprocess.Popen(args, stderr=subprocess.PIPE)
            cursor_in_new_line = True
            proc_stderr_closed = False
            while not proc_stderr_closed:
                # read line from stderr
                line = u''
                while True:
                    char = proc.stderr.read(1)
                    if not char:
                        proc_stderr_closed = True
                        break
                    if char in [b'\r', b'\n']:
                        break
                    line += char.decode('ascii', 'replace')
                if not line:
                    # proc_stderr_closed is True
                    continue
                mobj = re.search(r'([0-9]+\.[0-9]{3}) kB / [0-9]+\.[0-9]{2} sec \(([0-9]{1,2}\.[0-9])%\)', line)
                if mobj:
                    downloaded_data_len = int(float(mobj.group(1))*1024)
                    percent = float(mobj.group(2))
                    if not resume_percent:
                        resume_percent = percent
                        resume_downloaded_data_len = downloaded_data_len
                    eta = self.calc_eta(start, time.time(), 100-resume_percent, percent-resume_percent)
                    speed = self.calc_speed(start, time.time(), downloaded_data_len-resume_downloaded_data_len)
                    data_len = None
                    if percent > 0:
                        data_len = int(downloaded_data_len * 100 / percent)
                    data_len_str = u'~' + format_bytes(data_len)
                    self.report_progress(percent, data_len_str, speed, eta)
                    cursor_in_new_line = False
                    self._hook_progress({
                        'downloaded_bytes': downloaded_data_len,
                        'total_bytes': data_len,
                        'tmpfilename': tmpfilename,
                        'filename': filename,
                        'status': 'downloading',
                        'eta': eta,
                        'speed': speed,
                    })
                else:
                    # no percent for live streams
                    mobj = re.search(r'([0-9]+\.[0-9]{3}) kB / [0-9]+\.[0-9]{2} sec', line)
                    if mobj:
                        downloaded_data_len = int(float(mobj.group(1))*1024)
                        time_now = time.time()
                        speed = self.calc_speed(start, time_now, downloaded_data_len)
                        self.report_progress_live_stream(downloaded_data_len, speed, time_now - start)
                        cursor_in_new_line = False
                        self._hook_progress({
                            'downloaded_bytes': downloaded_data_len,
                            'tmpfilename': tmpfilename,
                            'filename': filename,
                            'status': 'downloading',
                            'speed': speed,
                        })
                    elif self.params.get('verbose', False):
                        if not cursor_in_new_line:
                            self.to_screen(u'')
                        cursor_in_new_line = True
                        self.to_screen(u'[rtmpdump] '+line)
            proc.wait()
            if not cursor_in_new_line:
                self.to_screen(u'')
            return proc.returncode

        url = info_dict['url']
        player_url = info_dict.get('player_url', None)
        page_url = info_dict.get('page_url', None)
        play_path = info_dict.get('play_path', None)
        tc_url = info_dict.get('tc_url', None)
        live = info_dict.get('rtmp_live', False)
        conn = info_dict.get('rtmp_conn', None)

        self.report_destination(filename)
        tmpfilename = self.temp_name(filename)
        test = self.params.get('test', False)

        # Check for rtmpdump first
        try:
            subprocess.call(['rtmpdump', '-h'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT)
        except (OSError, IOError):
            self.report_error(u'RTMP download detected but "rtmpdump" could not be run')
            return False

        # Download using rtmpdump. rtmpdump returns exit code 2 when
        # the connection was interrumpted and resuming appears to be
        # possible. This is part of rtmpdump's normal usage, AFAIK.
        basic_args = ['rtmpdump', '--verbose', '-r', url, '-o', tmpfilename]
        if player_url is not None:
            basic_args += ['--swfVfy', player_url]
        if page_url is not None:
            basic_args += ['--pageUrl', page_url]
        if play_path is not None:
            basic_args += ['--playpath', play_path]
        if tc_url is not None:
            basic_args += ['--tcUrl', url]
        if test:
            basic_args += ['--stop', '1']
        if live:
            basic_args += ['--live']
        if conn:
            basic_args += ['--conn', conn]
        args = basic_args + [[], ['--resume', '--skip', '1']][self.params.get('continuedl', False)]

        if sys.platform == 'win32' and sys.version_info < (3, 0):
            # Windows subprocess module does not actually support Unicode
            # on Python 2.x
            # See http://stackoverflow.com/a/9951851/35070
            subprocess_encoding = sys.getfilesystemencoding()
            args = [a.encode(subprocess_encoding, 'ignore') for a in args]
        else:
            subprocess_encoding = None

        if self.params.get('verbose', False):
            if subprocess_encoding:
                str_args = [
                    a.decode(subprocess_encoding) if isinstance(a, bytes) else a
                    for a in args]
            else:
                str_args = args
            try:
                import pipes
                shell_quote = lambda args: ' '.join(map(pipes.quote, str_args))
            except ImportError:
                shell_quote = repr
            self.to_screen(u'[debug] rtmpdump command line: ' + shell_quote(str_args))

        retval = run_rtmpdump(args)

        while (retval == 2 or retval == 1) and not test:
            prevsize = os.path.getsize(encodeFilename(tmpfilename))
            self.to_screen(u'[rtmpdump] %s bytes' % prevsize)
            time.sleep(5.0) # This seems to be needed
            retval = run_rtmpdump(basic_args + ['-e'] + [[], ['-k', '1']][retval == 1])
            cursize = os.path.getsize(encodeFilename(tmpfilename))
            if prevsize == cursize and retval == 1:
                break
             # Some rtmp streams seem abort after ~ 99.8%. Don't complain for those
            if prevsize == cursize and retval == 2 and cursize > 1024:
                self.to_screen(u'[rtmpdump] Could not download the whole video. This can happen for some advertisements.')
                retval = 0
                break
        if retval == 0 or (test and retval == 2):
            fsize = os.path.getsize(encodeFilename(tmpfilename))
            self.to_screen(u'[rtmpdump] %s bytes' % fsize)
            self.try_rename(tmpfilename, filename)
            self._hook_progress({
                'downloaded_bytes': fsize,
                'total_bytes': fsize,
                'filename': filename,
                'status': 'finished',
            })
            return True
        else:
            self.to_stderr(u"\n")
            self.report_error(u'rtmpdump exited with code %d' % retval)
            return False