]>
Raphaƫl G. Git Repositories - youtubedl/blob - youtube_dl/downloader/fragment.py
f2e5733b6406603f9a52b26b65bd4bc3cc833fec
   1 from __future__ 
import division
, unicode_literals
 
   7 from .common 
import FileDownloader
 
   8 from .http 
import HttpFD
 
  17 class HttpQuietDownloader(HttpFD
): 
  18     def to_screen(self
, *args
, **kargs
): 
  22 class FragmentFD(FileDownloader
): 
  24     A base file downloader class for fragmented media (e.g. f4m/m3u8 manifests). 
  28     fragment_retries:   Number of times to retry a fragment for HTTP error (DASH 
  30     skip_unavailable_fragments: 
  31                         Skip unavailable fragments (DASH and hlsnative only) 
  32     keep_fragments:     Keep downloaded fragments on disk after downloading is 
  35     For each incomplete fragment download youtube-dl keeps on disk a special 
  36     bookkeeping file with download state and metadata (in future such files will 
  37     be used for any incomplete download handled by youtube-dl). This file is 
  38     used to properly handle resuming, check download file consistency and detect 
  39     potential errors. The file has a .ytdl extension and represents a standard 
  40     JSON file of the following format: 
  43         Dictionary of extractor related data. TBD. 
  46         Dictionary of downloader related data. May contain following data: 
  48                 Dictionary with current (being downloaded) fragment data: 
  49                 index:  0-based index of current fragment among all fragments 
  51                 Total count of fragments 
  53     This feature is experimental and file format may change in future. 
  56     def report_retry_fragment(self
, err
, frag_index
, count
, retries
): 
  58             '[download] Got server HTTP error: %s. Retrying fragment %d (attempt %d of %s)...' 
  59             % (error_to_compat_str(err
), frag_index
, count
, self
.format_retries(retries
))) 
  61     def report_skip_fragment(self
, frag_index
): 
  62         self
.to_screen('[download] Skipping fragment %d...' % frag_index
) 
  64     def _prepare_url(self
, info_dict
, url
): 
  65         headers 
= info_dict
.get('http_headers') 
  66         return sanitized_Request(url
, None, headers
) if headers 
else url
 
  68     def _prepare_and_start_frag_download(self
, ctx
): 
  69         self
._prepare
_frag
_download
(ctx
) 
  70         self
._start
_frag
_download
(ctx
) 
  73     def __do_ytdl_file(ctx
): 
  74         return not ctx
['live'] and not ctx
['tmpfilename'] == '-' 
  76     def _read_ytdl_file(self
, ctx
): 
  77         assert 'ytdl_corrupt' not in ctx
 
  78         stream
, _ 
= sanitize_open(self
.ytdl_filename(ctx
['filename']), 'r') 
  80             ctx
['fragment_index'] = json
.loads(stream
.read())['downloader']['current_fragment']['index'] 
  82             ctx
['ytdl_corrupt'] = True 
  86     def _write_ytdl_file(self
, ctx
): 
  87         frag_index_stream
, _ 
= sanitize_open(self
.ytdl_filename(ctx
['filename']), 'w') 
  90                 'index': ctx
['fragment_index'], 
  93         if ctx
.get('fragment_count') is not None: 
  94             downloader
['fragment_count'] = ctx
['fragment_count'] 
  95         frag_index_stream
.write(json
.dumps({'downloader': downloader
})) 
  96         frag_index_stream
.close() 
  98     def _download_fragment(self
, ctx
, frag_url
, info_dict
, headers
=None): 
  99         fragment_filename 
= '%s-Frag%d' % (ctx
['tmpfilename'], ctx
['fragment_index']) 
 100         success 
= ctx
['dl'].download(fragment_filename
, { 
 102             'http_headers': headers 
or info_dict
.get('http_headers'), 
 106         down
, frag_sanitized 
= sanitize_open(fragment_filename
, 'rb') 
 107         ctx
['fragment_filename_sanitized'] = frag_sanitized
 
 108         frag_content 
= down
.read() 
 110         return True, frag_content
 
 112     def _append_fragment(self
, ctx
, frag_content
): 
 114             ctx
['dest_stream'].write(frag_content
) 
 115             ctx
['dest_stream'].flush() 
 117             if self
.__do
_ytdl
_file
(ctx
): 
 118                 self
._write
_ytdl
_file
(ctx
) 
 119             if not self
.params
.get('keep_fragments', False): 
 120                 os
.remove(encodeFilename(ctx
['fragment_filename_sanitized'])) 
 121             del ctx
['fragment_filename_sanitized'] 
 123     def _prepare_frag_download(self
, ctx
): 
 124         if 'live' not in ctx
: 
 127             total_frags_str 
= '%d' % ctx
['total_frags'] 
 128             ad_frags 
= ctx
.get('ad_frags', 0) 
 130                 total_frags_str 
+= ' (not including %d ad)' % ad_frags
 
 132             total_frags_str 
= 'unknown (live)' 
 134             '[%s] Total fragments: %s' % (self
.FD_NAME
, total_frags_str
)) 
 135         self
.report_destination(ctx
['filename']) 
 136         dl 
= HttpQuietDownloader( 
 142                 'ratelimit': self
.params
.get('ratelimit'), 
 143                 'retries': self
.params
.get('retries', 0), 
 144                 'nopart': self
.params
.get('nopart', False), 
 145                 'test': self
.params
.get('test', False), 
 148         tmpfilename 
= self
.temp_name(ctx
['filename']) 
 152         # Establish possible resume length 
 153         if os
.path
.isfile(encodeFilename(tmpfilename
)): 
 155             resume_len 
= os
.path
.getsize(encodeFilename(tmpfilename
)) 
 157         # Should be initialized before ytdl file check 
 159             'tmpfilename': tmpfilename
, 
 163         if self
.__do
_ytdl
_file
(ctx
): 
 164             if os
.path
.isfile(encodeFilename(self
.ytdl_filename(ctx
['filename']))): 
 165                 self
._read
_ytdl
_file
(ctx
) 
 166                 is_corrupt 
= ctx
.get('ytdl_corrupt') is True 
 167                 is_inconsistent 
= ctx
['fragment_index'] > 0 and resume_len 
== 0 
 168                 if is_corrupt 
or is_inconsistent
: 
 170                         '.ytdl file is corrupt' if is_corrupt 
else 
 171                         'Inconsistent state of incomplete fragment download') 
 173                         '%s. Restarting from the beginning...' % message
) 
 174                     ctx
['fragment_index'] = resume_len 
= 0 
 175                     if 'ytdl_corrupt' in ctx
: 
 176                         del ctx
['ytdl_corrupt'] 
 177                     self
._write
_ytdl
_file
(ctx
) 
 179                 self
._write
_ytdl
_file
(ctx
) 
 180                 assert ctx
['fragment_index'] == 0 
 182         dest_stream
, tmpfilename 
= sanitize_open(tmpfilename
, open_mode
) 
 186             'dest_stream': dest_stream
, 
 187             'tmpfilename': tmpfilename
, 
 188             # Total complete fragments downloaded so far in bytes 
 189             'complete_frags_downloaded_bytes': resume_len
, 
 192     def _start_frag_download(self
, ctx
): 
 193         total_frags 
= ctx
['total_frags'] 
 194         # This dict stores the download progress, it's updated by the progress 
 197             'status': 'downloading', 
 198             'downloaded_bytes': ctx
['complete_frags_downloaded_bytes'], 
 199             'fragment_index': ctx
['fragment_index'], 
 200             'fragment_count': total_frags
, 
 201             'filename': ctx
['filename'], 
 202             'tmpfilename': ctx
['tmpfilename'], 
 208             # Amount of fragment's bytes downloaded by the time of the previous 
 209             # frag progress hook invocation 
 210             'prev_frag_downloaded_bytes': 0, 
 213         def frag_progress_hook(s
): 
 214             if s
['status'] not in ('downloading', 'finished'): 
 217             time_now 
= time
.time() 
 218             state
['elapsed'] = time_now 
- start
 
 219             frag_total_bytes 
= s
.get('total_bytes') or 0 
 222                     (ctx
['complete_frags_downloaded_bytes'] + frag_total_bytes
) 
 223                     / (state
['fragment_index'] + 1) * total_frags
) 
 224                 state
['total_bytes_estimate'] = estimated_size
 
 226             if s
['status'] == 'finished': 
 227                 state
['fragment_index'] += 1 
 228                 ctx
['fragment_index'] = state
['fragment_index'] 
 229                 state
['downloaded_bytes'] += frag_total_bytes 
- ctx
['prev_frag_downloaded_bytes'] 
 230                 ctx
['complete_frags_downloaded_bytes'] = state
['downloaded_bytes'] 
 231                 ctx
['prev_frag_downloaded_bytes'] = 0 
 233                 frag_downloaded_bytes 
= s
['downloaded_bytes'] 
 234                 state
['downloaded_bytes'] += frag_downloaded_bytes 
- ctx
['prev_frag_downloaded_bytes'] 
 236                     state
['eta'] = self
.calc_eta( 
 237                         start
, time_now
, estimated_size
, 
 238                         state
['downloaded_bytes']) 
 239                 state
['speed'] = s
.get('speed') or ctx
.get('speed') 
 240                 ctx
['speed'] = state
['speed'] 
 241                 ctx
['prev_frag_downloaded_bytes'] = frag_downloaded_bytes
 
 242             self
._hook
_progress
(state
) 
 244         ctx
['dl'].add_progress_hook(frag_progress_hook
) 
 248     def _finish_frag_download(self
, ctx
): 
 249         ctx
['dest_stream'].close() 
 250         if self
.__do
_ytdl
_file
(ctx
): 
 251             ytdl_filename 
= encodeFilename(self
.ytdl_filename(ctx
['filename'])) 
 252             if os
.path
.isfile(ytdl_filename
): 
 253                 os
.remove(ytdl_filename
) 
 254         elapsed 
= time
.time() - ctx
['started'] 
 256         if ctx
['tmpfilename'] == '-': 
 257             downloaded_bytes 
= ctx
['complete_frags_downloaded_bytes'] 
 259             self
.try_rename(ctx
['tmpfilename'], ctx
['filename']) 
 260             downloaded_bytes 
= os
.path
.getsize(encodeFilename(ctx
['filename'])) 
 262         self
._hook
_progress
({ 
 263             'downloaded_bytes': downloaded_bytes
, 
 264             'total_bytes': downloaded_bytes
, 
 265             'filename': ctx
['filename'], 
 266             'status': 'finished',