]>
Raphaƫl G. Git Repositories - youtubedl/blob - youtube_dl/downloader/fragment.py
ea5e3a4b5df9f957328557b6f822ef5494cfc9a6
   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
) 
 110             ctx
['dest_stream'].flush() 
 112             if self
.__do
_ytdl
_file
(ctx
): 
 113                 self
._write
_ytdl
_file
(ctx
) 
 114             if not self
.params
.get('keep_fragments', False): 
 115                 os
.remove(encodeFilename(ctx
['fragment_filename_sanitized'])) 
 116             del ctx
['fragment_filename_sanitized'] 
 118     def _prepare_frag_download(self
, ctx
): 
 119         if 'live' not in ctx
: 
 122             total_frags_str 
= '%d' % ctx
['total_frags'] 
 123             ad_frags 
= ctx
.get('ad_frags', 0) 
 125                 total_frags_str 
+= ' (not including %d ad)' % ad_frags
 
 127             total_frags_str 
= 'unknown (live)' 
 129             '[%s] Total fragments: %s' % (self
.FD_NAME
, total_frags_str
)) 
 130         self
.report_destination(ctx
['filename']) 
 131         dl 
= HttpQuietDownloader( 
 137                 'ratelimit': self
.params
.get('ratelimit'), 
 138                 'retries': self
.params
.get('retries', 0), 
 139                 'nopart': self
.params
.get('nopart', False), 
 140                 'test': self
.params
.get('test', False), 
 143         tmpfilename 
= self
.temp_name(ctx
['filename']) 
 147         # Establish possible resume length 
 148         if os
.path
.isfile(encodeFilename(tmpfilename
)): 
 150             resume_len 
= os
.path
.getsize(encodeFilename(tmpfilename
)) 
 152         # Should be initialized before ytdl file check 
 154             'tmpfilename': tmpfilename
, 
 158         if self
.__do
_ytdl
_file
(ctx
): 
 159             if os
.path
.isfile(encodeFilename(self
.ytdl_filename(ctx
['filename']))): 
 160                 self
._read
_ytdl
_file
(ctx
) 
 161                 if ctx
['fragment_index'] > 0 and resume_len 
== 0: 
 163                         'Inconsistent state of incomplete fragment download. ' 
 164                         'Restarting from the beginning...') 
 165                     ctx
['fragment_index'] = resume_len 
= 0 
 166                     self
._write
_ytdl
_file
(ctx
) 
 168                 self
._write
_ytdl
_file
(ctx
) 
 169                 assert ctx
['fragment_index'] == 0 
 171         dest_stream
, tmpfilename 
= sanitize_open(tmpfilename
, open_mode
) 
 175             'dest_stream': dest_stream
, 
 176             'tmpfilename': tmpfilename
, 
 177             # Total complete fragments downloaded so far in bytes 
 178             'complete_frags_downloaded_bytes': resume_len
, 
 181     def _start_frag_download(self
, ctx
): 
 182         total_frags 
= ctx
['total_frags'] 
 183         # This dict stores the download progress, it's updated by the progress 
 186             'status': 'downloading', 
 187             'downloaded_bytes': ctx
['complete_frags_downloaded_bytes'], 
 188             'fragment_index': ctx
['fragment_index'], 
 189             'fragment_count': total_frags
, 
 190             'filename': ctx
['filename'], 
 191             'tmpfilename': ctx
['tmpfilename'], 
 197             # Amount of fragment's bytes downloaded by the time of the previous 
 198             # frag progress hook invocation 
 199             'prev_frag_downloaded_bytes': 0, 
 202         def frag_progress_hook(s
): 
 203             if s
['status'] not in ('downloading', 'finished'): 
 206             time_now 
= time
.time() 
 207             state
['elapsed'] = time_now 
- start
 
 208             frag_total_bytes 
= s
.get('total_bytes') or 0 
 211                     (ctx
['complete_frags_downloaded_bytes'] + frag_total_bytes
) / 
 212                     (state
['fragment_index'] + 1) * total_frags
) 
 213                 state
['total_bytes_estimate'] = estimated_size
 
 215             if s
['status'] == 'finished': 
 216                 state
['fragment_index'] += 1 
 217                 ctx
['fragment_index'] = state
['fragment_index'] 
 218                 state
['downloaded_bytes'] += frag_total_bytes 
- ctx
['prev_frag_downloaded_bytes'] 
 219                 ctx
['complete_frags_downloaded_bytes'] = state
['downloaded_bytes'] 
 220                 ctx
['prev_frag_downloaded_bytes'] = 0 
 222                 frag_downloaded_bytes 
= s
['downloaded_bytes'] 
 223                 state
['downloaded_bytes'] += frag_downloaded_bytes 
- ctx
['prev_frag_downloaded_bytes'] 
 225                     state
['eta'] = self
.calc_eta( 
 226                         start
, time_now
, estimated_size
, 
 227                         state
['downloaded_bytes']) 
 228                 state
['speed'] = s
.get('speed') or ctx
.get('speed') 
 229                 ctx
['speed'] = state
['speed'] 
 230                 ctx
['prev_frag_downloaded_bytes'] = frag_downloaded_bytes
 
 231             self
._hook
_progress
(state
) 
 233         ctx
['dl'].add_progress_hook(frag_progress_hook
) 
 237     def _finish_frag_download(self
, ctx
): 
 238         ctx
['dest_stream'].close() 
 239         if self
.__do
_ytdl
_file
(ctx
): 
 240             ytdl_filename 
= encodeFilename(self
.ytdl_filename(ctx
['filename'])) 
 241             if os
.path
.isfile(ytdl_filename
): 
 242                 os
.remove(ytdl_filename
) 
 243         elapsed 
= time
.time() - ctx
['started'] 
 244         self
.try_rename(ctx
['tmpfilename'], ctx
['filename']) 
 245         fsize 
= os
.path
.getsize(encodeFilename(ctx
['filename'])) 
 247         self
._hook
_progress
({ 
 248             'downloaded_bytes': fsize
, 
 249             'total_bytes': fsize
, 
 250             'filename': ctx
['filename'], 
 251             'status': 'finished',