]>
Raphaƫl G. Git Repositories - youtubedl/blob - youtube_dl/downloader/fragment.py
7e891b92a3b6a05257484c5d8ba81590013bb74b
   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         stream
, _ 
= sanitize_open(self
.ytdl_filename(ctx
['filename']), 'r') 
  78         ctx
['fragment_index'] = json
.loads(stream
.read())['downloader']['current_fragment']['index'] 
  81     def _write_ytdl_file(self
, ctx
): 
  82         frag_index_stream
, _ 
= sanitize_open(self
.ytdl_filename(ctx
['filename']), 'w') 
  85                 'index': ctx
['fragment_index'], 
  88         if ctx
.get('fragment_count') is not None: 
  89             downloader
['fragment_count'] = ctx
['fragment_count'] 
  90         frag_index_stream
.write(json
.dumps({'downloader': downloader
})) 
  91         frag_index_stream
.close() 
  93     def _download_fragment(self
, ctx
, frag_url
, info_dict
, headers
=None): 
  94         fragment_filename 
= '%s-Frag%d' % (ctx
['tmpfilename'], ctx
['fragment_index']) 
  95         success 
= ctx
['dl'].download(fragment_filename
, { 
  97             'http_headers': headers 
or info_dict
.get('http_headers'), 
 101         down
, frag_sanitized 
= sanitize_open(fragment_filename
, 'rb') 
 102         ctx
['fragment_filename_sanitized'] = frag_sanitized
 
 103         frag_content 
= down
.read() 
 105         return True, frag_content
 
 107     def _append_fragment(self
, ctx
, frag_content
): 
 109             ctx
['dest_stream'].write(frag_content
) 
 111             if self
.__do
_ytdl
_file
(ctx
): 
 112                 self
._write
_ytdl
_file
(ctx
) 
 113             if not self
.params
.get('keep_fragments', False): 
 114                 os
.remove(ctx
['fragment_filename_sanitized']) 
 115             del ctx
['fragment_filename_sanitized'] 
 117     def _prepare_frag_download(self
, ctx
): 
 118         if 'live' not in ctx
: 
 121             total_frags_str 
= '%d' % ctx
['total_frags'] 
 122             ad_frags 
= ctx
.get('ad_frags', 0) 
 124                 total_frags_str 
+= ' (not including %d ad)' % ad_frags
 
 126             total_frags_str 
= 'unknown (live)' 
 128             '[%s] Total fragments: %s' % (self
.FD_NAME
, total_frags_str
)) 
 129         self
.report_destination(ctx
['filename']) 
 130         dl 
= HttpQuietDownloader( 
 136                 'ratelimit': self
.params
.get('ratelimit'), 
 137                 'retries': self
.params
.get('retries', 0), 
 138                 'nopart': self
.params
.get('nopart', False), 
 139                 'test': self
.params
.get('test', False), 
 142         tmpfilename 
= self
.temp_name(ctx
['filename']) 
 146         # Establish possible resume length 
 147         if os
.path
.isfile(encodeFilename(tmpfilename
)): 
 149             resume_len 
= os
.path
.getsize(encodeFilename(tmpfilename
)) 
 151         # Should be initialized before ytdl file check 
 153             'tmpfilename': tmpfilename
, 
 157         if self
.__do
_ytdl
_file
(ctx
): 
 158             if os
.path
.isfile(encodeFilename(self
.ytdl_filename(ctx
['filename']))): 
 159                 self
._read
_ytdl
_file
(ctx
) 
 160                 if ctx
['fragment_index'] > 0 and resume_len 
== 0: 
 162                         'Inconsistent state of incomplete fragment download. ' 
 163                         'Restarting from the beginning...') 
 164                     ctx
['fragment_index'] = resume_len 
= 0 
 165                     self
._write
_ytdl
_file
(ctx
) 
 167                 self
._write
_ytdl
_file
(ctx
) 
 168                 assert ctx
['fragment_index'] == 0 
 170         dest_stream
, tmpfilename 
= sanitize_open(tmpfilename
, open_mode
) 
 174             'dest_stream': dest_stream
, 
 175             'tmpfilename': tmpfilename
, 
 176             # Total complete fragments downloaded so far in bytes 
 177             'complete_frags_downloaded_bytes': resume_len
, 
 180     def _start_frag_download(self
, ctx
): 
 181         total_frags 
= ctx
['total_frags'] 
 182         # This dict stores the download progress, it's updated by the progress 
 185             'status': 'downloading', 
 186             'downloaded_bytes': ctx
['complete_frags_downloaded_bytes'], 
 187             'fragment_index': ctx
['fragment_index'], 
 188             'fragment_count': total_frags
, 
 189             'filename': ctx
['filename'], 
 190             'tmpfilename': ctx
['tmpfilename'], 
 196             # Amount of fragment's bytes downloaded by the time of the previous 
 197             # frag progress hook invocation 
 198             'prev_frag_downloaded_bytes': 0, 
 201         def frag_progress_hook(s
): 
 202             if s
['status'] not in ('downloading', 'finished'): 
 205             time_now 
= time
.time() 
 206             state
['elapsed'] = time_now 
- start
 
 207             frag_total_bytes 
= s
.get('total_bytes') or 0 
 210                     (ctx
['complete_frags_downloaded_bytes'] + frag_total_bytes
) / 
 211                     (state
['fragment_index'] + 1) * total_frags
) 
 212                 state
['total_bytes_estimate'] = estimated_size
 
 214             if s
['status'] == 'finished': 
 215                 state
['fragment_index'] += 1 
 216                 ctx
['fragment_index'] = state
['fragment_index'] 
 217                 state
['downloaded_bytes'] += frag_total_bytes 
- ctx
['prev_frag_downloaded_bytes'] 
 218                 ctx
['complete_frags_downloaded_bytes'] = state
['downloaded_bytes'] 
 219                 ctx
['prev_frag_downloaded_bytes'] = 0 
 221                 frag_downloaded_bytes 
= s
['downloaded_bytes'] 
 222                 state
['downloaded_bytes'] += frag_downloaded_bytes 
- ctx
['prev_frag_downloaded_bytes'] 
 224                     state
['eta'] = self
.calc_eta( 
 225                         start
, time_now
, estimated_size
, 
 226                         state
['downloaded_bytes']) 
 227                 state
['speed'] = s
.get('speed') or ctx
.get('speed') 
 228                 ctx
['speed'] = state
['speed'] 
 229                 ctx
['prev_frag_downloaded_bytes'] = frag_downloaded_bytes
 
 230             self
._hook
_progress
(state
) 
 232         ctx
['dl'].add_progress_hook(frag_progress_hook
) 
 236     def _finish_frag_download(self
, ctx
): 
 237         ctx
['dest_stream'].close() 
 238         if self
.__do
_ytdl
_file
(ctx
): 
 239             ytdl_filename 
= encodeFilename(self
.ytdl_filename(ctx
['filename'])) 
 240             if os
.path
.isfile(ytdl_filename
): 
 241                 os
.remove(ytdl_filename
) 
 242         elapsed 
= time
.time() - ctx
['started'] 
 243         self
.try_rename(ctx
['tmpfilename'], ctx
['filename']) 
 244         fsize 
= os
.path
.getsize(encodeFilename(ctx
['filename'])) 
 246         self
._hook
_progress
({ 
 247             'downloaded_bytes': fsize
, 
 248             'total_bytes': fsize
, 
 249             'filename': ctx
['filename'], 
 250             'status': 'finished',