2 # -*- coding: utf-8 -*-
22 import urllib.request as compat_urllib_request
23 except ImportError: # Python 2
24 import urllib2 as compat_urllib_request
27 import urllib.error as compat_urllib_error
28 except ImportError: # Python 2
29 import urllib2 as compat_urllib_error
32 import urllib.parse as compat_urllib_parse
33 except ImportError: # Python 2
34 import urllib as compat_urllib_parse
37 from urllib.parse import urlparse as compat_urllib_parse_urlparse
38 except ImportError: # Python 2
39 from urlparse import urlparse as compat_urllib_parse_urlparse
42 import urllib.parse as compat_urlparse
43 except ImportError: # Python 2
44 import urlparse as compat_urlparse
47 import http.cookiejar as compat_cookiejar
48 except ImportError: # Python 2
49 import cookielib as compat_cookiejar
52 import html.entities as compat_html_entities
53 except ImportError: # Python 2
54 import htmlentitydefs as compat_html_entities
57 import html.parser as compat_html_parser
58 except ImportError: # Python 2
59 import HTMLParser as compat_html_parser
62 import http.client as compat_http_client
63 except ImportError: # Python 2
64 import httplib as compat_http_client
67 from urllib.error import HTTPError as compat_HTTPError
68 except ImportError: # Python 2
69 from urllib2 import HTTPError as compat_HTTPError
72 from urllib.request import urlretrieve as compat_urlretrieve
73 except ImportError: # Python 2
74 from urllib import urlretrieve as compat_urlretrieve
78 from subprocess import DEVNULL
79 compat_subprocess_get_DEVNULL = lambda: DEVNULL
81 compat_subprocess_get_DEVNULL = lambda: open(os.path.devnull, 'w')
84 from urllib.parse import parse_qs as compat_parse_qs
85 except ImportError: # Python 2
86 # HACK: The following is the correct parse_qs implementation from cpython 3's stdlib.
87 # Python 2's version is apparently totally broken
88 def _unquote(string, encoding='utf-8', errors='replace'):
91 res = string.split('%')
98 # pct_sequence: contiguous sequence of percent-encoded bytes, decoded
105 pct_sequence += item[:2].decode('hex')
108 # This segment was just a single percent-encoded character.
109 # May be part of a sequence of code units, so delay decoding.
110 # (Stored in pct_sequence).
114 # Encountered non-percent-encoded characters. Flush the current
116 string += pct_sequence.decode(encoding, errors) + rest
119 # Flush the final pct_sequence
120 string += pct_sequence.decode(encoding, errors)
123 def _parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
124 encoding='utf-8', errors='replace'):
125 qs, _coerce_result = qs, unicode
126 pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
128 for name_value in pairs:
129 if not name_value and not strict_parsing:
131 nv = name_value.split('=', 1)
134 raise ValueError("bad query field: %r" % (name_value,))
135 # Handle case of a control-name with no equal sign
136 if keep_blank_values:
140 if len(nv[1]) or keep_blank_values:
141 name = nv[0].replace('+', ' ')
142 name = _unquote(name, encoding=encoding, errors=errors)
143 name = _coerce_result(name)
144 value = nv[1].replace('+', ' ')
145 value = _unquote(value, encoding=encoding, errors=errors)
146 value = _coerce_result(value)
147 r.append((name, value))
150 def compat_parse_qs(qs, keep_blank_values=False, strict_parsing=False,
151 encoding='utf-8', errors='replace'):
153 pairs = _parse_qsl(qs, keep_blank_values, strict_parsing,
154 encoding=encoding, errors=errors)
155 for name, value in pairs:
156 if name in parsed_result:
157 parsed_result[name].append(value)
159 parsed_result[name] = [value]
163 compat_str = unicode # Python 2
168 compat_chr = unichr # Python 2
173 if type(c) is int: return c
176 # This is not clearly defined otherwise
177 compiled_regex_type = type(re.compile(''))
180 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0 (Chrome)',
181 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
182 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
183 'Accept-Encoding': 'gzip, deflate',
184 'Accept-Language': 'en-us,en;q=0.5',
187 def preferredencoding():
188 """Get preferred encoding.
190 Returns the best encoding scheme for the system, based on
191 locale.getpreferredencoding() and some further tweaks.
194 pref = locale.getpreferredencoding()
201 if sys.version_info < (3,0):
203 print(s.encode(preferredencoding(), 'xmlcharrefreplace'))
206 assert type(s) == type(u'')
209 # In Python 2.x, json.dump expects a bytestream.
210 # In Python 3.x, it writes to a character stream
211 if sys.version_info < (3,0):
212 def write_json_file(obj, fn):
213 with open(fn, 'wb') as f:
216 def write_json_file(obj, fn):
217 with open(fn, 'w', encoding='utf-8') as f:
220 if sys.version_info >= (2,7):
221 def find_xpath_attr(node, xpath, key, val):
222 """ Find the xpath xpath[@key=val] """
223 assert re.match(r'^[a-zA-Z]+$', key)
224 assert re.match(r'^[a-zA-Z0-9@\s]*$', val)
225 expr = xpath + u"[@%s='%s']" % (key, val)
226 return node.find(expr)
228 def find_xpath_attr(node, xpath, key, val):
229 for f in node.findall(xpath):
230 if f.attrib.get(key) == val:
234 # On python2.6 the xml.etree.ElementTree.Element methods don't support
235 # the namespace parameter
236 def xpath_with_ns(path, ns_map):
237 components = [c.split(':') for c in path.split('/')]
241 replaced.append(c[0])
244 replaced.append('{%s}%s' % (ns_map[ns], tag))
245 return '/'.join(replaced)
247 def htmlentity_transform(matchobj):
248 """Transforms an HTML entity to a character.
250 This function receives a match object and is intended to be used with
251 the re.sub() function.
253 entity = matchobj.group(1)
255 # Known non-numeric HTML entity
256 if entity in compat_html_entities.name2codepoint:
257 return compat_chr(compat_html_entities.name2codepoint[entity])
259 mobj = re.match(u'(?u)#(x?\\d+)', entity)
261 numstr = mobj.group(1)
262 if numstr.startswith(u'x'):
264 numstr = u'0%s' % numstr
267 return compat_chr(int(numstr, base))
269 # Unknown entity in name, return its literal representation
270 return (u'&%s;' % entity)
272 compat_html_parser.locatestarttagend = re.compile(r"""<[a-zA-Z][-.a-zA-Z0-9:_]*(?:\s+(?:(?<=['"\s])[^\s/>][^\s/=>]*(?:\s*=+\s*(?:'[^']*'|"[^"]*"|(?!['"])[^>\s]*))?\s*)*)?\s*""", re.VERBOSE) # backport bugfix
273 class BaseHTMLParser(compat_html_parser.HTMLParser):
275 compat_html_parser.HTMLParser.__init__(self)
278 def loads(self, html):
283 class AttrParser(BaseHTMLParser):
284 """Modified HTMLParser that isolates a tag with the specified attribute"""
285 def __init__(self, attribute, value):
286 self.attribute = attribute
291 self.watch_startpos = False
293 BaseHTMLParser.__init__(self)
295 def error(self, message):
296 if self.error_count > 10 or self.started:
297 raise compat_html_parser.HTMLParseError(message, self.getpos())
298 self.rawdata = '\n'.join(self.html.split('\n')[self.getpos()[0]:]) # skip one line
299 self.error_count += 1
302 def handle_starttag(self, tag, attrs):
305 self.find_startpos(None)
306 if self.attribute in attrs and attrs[self.attribute] == self.value:
309 self.watch_startpos = True
311 if not tag in self.depth: self.depth[tag] = 0
314 def handle_endtag(self, tag):
316 if tag in self.depth: self.depth[tag] -= 1
317 if self.depth[self.result[0]] == 0:
319 self.result.append(self.getpos())
321 def find_startpos(self, x):
322 """Needed to put the start position of the result (self.result[1])
323 after the opening tag with the requested id"""
324 if self.watch_startpos:
325 self.watch_startpos = False
326 self.result.append(self.getpos())
327 handle_entityref = handle_charref = handle_data = handle_comment = \
328 handle_decl = handle_pi = unknown_decl = find_startpos
330 def get_result(self):
331 if self.result is None:
333 if len(self.result) != 3:
335 lines = self.html.split('\n')
336 lines = lines[self.result[1][0]-1:self.result[2][0]]
337 lines[0] = lines[0][self.result[1][1]:]
339 lines[-1] = lines[-1][:self.result[2][1]-self.result[1][1]]
340 lines[-1] = lines[-1][:self.result[2][1]]
341 return '\n'.join(lines).strip()
342 # Hack for https://github.com/rg3/youtube-dl/issues/662
343 if sys.version_info < (2, 7, 3):
344 AttrParser.parse_endtag = (lambda self, i:
345 i + len("</scr'+'ipt>")
346 if self.rawdata[i:].startswith("</scr'+'ipt>")
347 else compat_html_parser.HTMLParser.parse_endtag(self, i))
349 def get_element_by_id(id, html):
350 """Return the content of the tag with the specified ID in the passed HTML document"""
351 return get_element_by_attribute("id", id, html)
353 def get_element_by_attribute(attribute, value, html):
354 """Return the content of the tag with the specified attribute in the passed HTML document"""
355 parser = AttrParser(attribute, value)
358 except compat_html_parser.HTMLParseError:
360 return parser.get_result()
362 class MetaParser(BaseHTMLParser):
364 Modified HTMLParser that isolates a meta tag with the specified name
367 def __init__(self, name):
368 BaseHTMLParser.__init__(self)
373 def handle_starttag(self, tag, attrs):
377 if attrs.get('name') == self.name:
378 self.result = attrs.get('content')
380 def get_result(self):
383 def get_meta_content(name, html):
385 Return the content attribute from the meta tag with the given name attribute.
387 parser = MetaParser(name)
390 except compat_html_parser.HTMLParseError:
392 return parser.get_result()
395 def clean_html(html):
396 """Clean an HTML snippet into a readable string"""
398 html = html.replace('\n', ' ')
399 html = re.sub(r'\s*<\s*br\s*/?\s*>\s*', '\n', html)
400 html = re.sub(r'<\s*/\s*p\s*>\s*<\s*p[^>]*>', '\n', html)
402 html = re.sub('<.*?>', '', html)
403 # Replace html entities
404 html = unescapeHTML(html)
408 def sanitize_open(filename, open_mode):
409 """Try to open the given filename, and slightly tweak it if this fails.
411 Attempts to open the given filename. If this fails, it tries to change
412 the filename slightly, step by step, until it's either able to open it
413 or it fails and raises a final exception, like the standard open()
416 It returns the tuple (stream, definitive_file_name).
420 if sys.platform == 'win32':
422 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
423 return (sys.stdout.buffer if hasattr(sys.stdout, 'buffer') else sys.stdout, filename)
424 stream = open(encodeFilename(filename), open_mode)
425 return (stream, filename)
426 except (IOError, OSError) as err:
427 if err.errno in (errno.EACCES,):
430 # In case of error, try to remove win32 forbidden chars
431 alt_filename = os.path.join(
432 re.sub(u'[/<>:"\\|\\\\?\\*]', u'#', path_part)
433 for path_part in os.path.split(filename)
435 if alt_filename == filename:
438 # An exception here should be caught in the caller
439 stream = open(encodeFilename(filename), open_mode)
440 return (stream, alt_filename)
443 def timeconvert(timestr):
444 """Convert RFC 2822 defined time string into system timestamp"""
446 timetuple = email.utils.parsedate_tz(timestr)
447 if timetuple is not None:
448 timestamp = email.utils.mktime_tz(timetuple)
451 def sanitize_filename(s, restricted=False, is_id=False):
452 """Sanitizes a string so it could be used as part of a filename.
453 If restricted is set, use a stricter subset of allowed characters.
454 Set is_id if this is not an arbitrary string, but an ID that should be kept if possible
456 def replace_insane(char):
457 if char == '?' or ord(char) < 32 or ord(char) == 127:
460 return '' if restricted else '\''
462 return '_-' if restricted else ' -'
463 elif char in '\\/|*<>':
465 if restricted and (char in '!&\'()[]{}$;`^,#' or char.isspace()):
467 if restricted and ord(char) > 127:
471 result = u''.join(map(replace_insane, s))
473 while '__' in result:
474 result = result.replace('__', '_')
475 result = result.strip('_')
476 # Common case of "Foreign band name - English song title"
477 if restricted and result.startswith('-_'):
483 def orderedSet(iterable):
484 """ Remove all duplicates from the input iterable """
495 assert type(s) == type(u'')
497 result = re.sub(u'(?u)&(.+?);', htmlentity_transform, s)
500 def encodeFilename(s):
502 @param s The name of the file
505 assert type(s) == type(u'')
507 # Python 3 has a Unicode API
508 if sys.version_info >= (3, 0):
511 if sys.platform == 'win32' and sys.getwindowsversion()[0] >= 5:
512 # Pass u'' directly to use Unicode APIs on Windows 2000 and up
513 # (Detecting Windows NT 4 is tricky because 'major >= 4' would
514 # match Windows 9x series as well. Besides, NT 4 is obsolete.)
517 encoding = sys.getfilesystemencoding()
520 return s.encode(encoding, 'ignore')
522 def decodeOption(optval):
525 if isinstance(optval, bytes):
526 optval = optval.decode(preferredencoding())
528 assert isinstance(optval, compat_str)
531 def formatSeconds(secs):
533 return '%d:%02d:%02d' % (secs // 3600, (secs % 3600) // 60, secs % 60)
535 return '%d:%02d' % (secs // 60, secs % 60)
539 def make_HTTPS_handler(opts_no_check_certificate):
540 if sys.version_info < (3, 2):
543 class HTTPSConnectionV3(httplib.HTTPSConnection):
544 def __init__(self, *args, **kwargs):
545 httplib.HTTPSConnection.__init__(self, *args, **kwargs)
548 sock = socket.create_connection((self.host, self.port), self.timeout)
549 if self._tunnel_host:
553 self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv3)
554 except ssl.SSLError as e:
555 self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv23)
557 class HTTPSHandlerV3(compat_urllib_request.HTTPSHandler):
558 def https_open(self, req):
559 return self.do_open(HTTPSConnectionV3, req)
560 return HTTPSHandlerV3()
562 context = ssl.SSLContext(ssl.PROTOCOL_SSLv3)
563 context.set_default_verify_paths()
565 context.verify_mode = (ssl.CERT_NONE
566 if opts_no_check_certificate
567 else ssl.CERT_REQUIRED)
568 return compat_urllib_request.HTTPSHandler(context=context)
570 class ExtractorError(Exception):
571 """Error during info extraction."""
572 def __init__(self, msg, tb=None, expected=False, cause=None):
573 """ tb, if given, is the original traceback (so that it can be printed out).
574 If expected is set, this is a normal error message and most likely not a bug in youtube-dl.
577 if sys.exc_info()[0] in (compat_urllib_error.URLError, socket.timeout, UnavailableVideoError):
580 msg = msg + u'; please report this issue on https://yt-dl.org/bug . Be sure to call youtube-dl with the --verbose flag and include its complete output. Make sure you are using the latest version; type youtube-dl -U to update.'
581 super(ExtractorError, self).__init__(msg)
584 self.exc_info = sys.exc_info() # preserve original exception
587 def format_traceback(self):
588 if self.traceback is None:
590 return u''.join(traceback.format_tb(self.traceback))
593 class RegexNotFoundError(ExtractorError):
594 """Error when a regex didn't match"""
598 class DownloadError(Exception):
599 """Download Error exception.
601 This exception may be thrown by FileDownloader objects if they are not
602 configured to continue on errors. They will contain the appropriate
605 def __init__(self, msg, exc_info=None):
606 """ exc_info, if given, is the original exception that caused the trouble (as returned by sys.exc_info()). """
607 super(DownloadError, self).__init__(msg)
608 self.exc_info = exc_info
611 class SameFileError(Exception):
612 """Same File exception.
614 This exception will be thrown by FileDownloader objects if they detect
615 multiple files would have to be downloaded to the same file on disk.
620 class PostProcessingError(Exception):
621 """Post Processing exception.
623 This exception may be raised by PostProcessor's .run() method to
624 indicate an error in the postprocessing task.
626 def __init__(self, msg):
629 class MaxDownloadsReached(Exception):
630 """ --max-downloads limit has been reached. """
634 class UnavailableVideoError(Exception):
635 """Unavailable Format exception.
637 This exception will be thrown when a video is requested
638 in a format that is not available for that video.
643 class ContentTooShortError(Exception):
644 """Content Too Short exception.
646 This exception may be raised by FileDownloader objects when a file they
647 download is too small for what the server announced first, indicating
648 the connection was probably interrupted.
654 def __init__(self, downloaded, expected):
655 self.downloaded = downloaded
656 self.expected = expected
658 class YoutubeDLHandler(compat_urllib_request.HTTPHandler):
659 """Handler for HTTP requests and responses.
661 This class, when installed with an OpenerDirector, automatically adds
662 the standard headers to every HTTP request and handles gzipped and
663 deflated responses from web servers. If compression is to be avoided in
664 a particular request, the original request in the program code only has
665 to include the HTTP header "Youtubedl-No-Compression", which will be
666 removed before making the real request.
668 Part of this code was copied from:
670 http://techknack.net/python-urllib2-handlers/
672 Andrew Rowls, the author of that code, agreed to release it to the
679 return zlib.decompress(data, -zlib.MAX_WBITS)
681 return zlib.decompress(data)
684 def addinfourl_wrapper(stream, headers, url, code):
685 if hasattr(compat_urllib_request.addinfourl, 'getcode'):
686 return compat_urllib_request.addinfourl(stream, headers, url, code)
687 ret = compat_urllib_request.addinfourl(stream, headers, url)
691 def http_request(self, req):
692 for h,v in std_headers.items():
696 if 'Youtubedl-no-compression' in req.headers:
697 if 'Accept-encoding' in req.headers:
698 del req.headers['Accept-encoding']
699 del req.headers['Youtubedl-no-compression']
700 if 'Youtubedl-user-agent' in req.headers:
701 if 'User-agent' in req.headers:
702 del req.headers['User-agent']
703 req.headers['User-agent'] = req.headers['Youtubedl-user-agent']
704 del req.headers['Youtubedl-user-agent']
707 def http_response(self, req, resp):
710 if resp.headers.get('Content-encoding', '') == 'gzip':
711 content = resp.read()
712 gz = gzip.GzipFile(fileobj=io.BytesIO(content), mode='rb')
714 uncompressed = io.BytesIO(gz.read())
715 except IOError as original_ioerror:
716 # There may be junk add the end of the file
717 # See http://stackoverflow.com/q/4928560/35070 for details
718 for i in range(1, 1024):
720 gz = gzip.GzipFile(fileobj=io.BytesIO(content[:-i]), mode='rb')
721 uncompressed = io.BytesIO(gz.read())
726 raise original_ioerror
727 resp = self.addinfourl_wrapper(uncompressed, old_resp.headers, old_resp.url, old_resp.code)
728 resp.msg = old_resp.msg
730 if resp.headers.get('Content-encoding', '') == 'deflate':
731 gz = io.BytesIO(self.deflate(resp.read()))
732 resp = self.addinfourl_wrapper(gz, old_resp.headers, old_resp.url, old_resp.code)
733 resp.msg = old_resp.msg
736 https_request = http_request
737 https_response = http_response
739 def unified_strdate(date_str):
740 """Return a string with the date in the format YYYYMMDD"""
743 date_str = date_str.replace(',',' ')
744 # %z (UTC offset) is only supported in python>=3.2
745 date_str = re.sub(r' (\+|-)[\d]*$', '', date_str)
746 format_expressions = [
754 '%Y-%m-%dT%H:%M:%SZ',
755 '%Y-%m-%dT%H:%M:%S.%fZ',
756 '%Y-%m-%dT%H:%M:%S.%f0Z',
759 for expression in format_expressions:
761 upload_date = datetime.datetime.strptime(date_str, expression).strftime('%Y%m%d')
766 def determine_ext(url, default_ext=u'unknown_video'):
767 guess = url.partition(u'?')[0].rpartition(u'.')[2]
768 if re.match(r'^[A-Za-z0-9]+$', guess):
773 def subtitles_filename(filename, sub_lang, sub_format):
774 return filename.rsplit('.', 1)[0] + u'.' + sub_lang + u'.' + sub_format
776 def date_from_str(date_str):
778 Return a datetime object from a string in the format YYYYMMDD or
779 (now|today)[+-][0-9](day|week|month|year)(s)?"""
780 today = datetime.date.today()
781 if date_str == 'now'or date_str == 'today':
783 match = re.match('(now|today)(?P<sign>[+-])(?P<time>\d+)(?P<unit>day|week|month|year)(s)?', date_str)
784 if match is not None:
785 sign = match.group('sign')
786 time = int(match.group('time'))
789 unit = match.group('unit')
798 delta = datetime.timedelta(**{unit: time})
800 return datetime.datetime.strptime(date_str, "%Y%m%d").date()
802 class DateRange(object):
803 """Represents a time interval between two dates"""
804 def __init__(self, start=None, end=None):
805 """start and end must be strings in the format accepted by date"""
806 if start is not None:
807 self.start = date_from_str(start)
809 self.start = datetime.datetime.min.date()
811 self.end = date_from_str(end)
813 self.end = datetime.datetime.max.date()
814 if self.start > self.end:
815 raise ValueError('Date range: "%s" , the start date must be before the end date' % self)
818 """Returns a range that only contains the given day"""
820 def __contains__(self, date):
821 """Check if the date is in the range"""
822 if not isinstance(date, datetime.date):
823 date = date_from_str(date)
824 return self.start <= date <= self.end
826 return '%s - %s' % ( self.start.isoformat(), self.end.isoformat())
830 """ Returns the platform name as a compat_str """
831 res = platform.platform()
832 if isinstance(res, bytes):
833 res = res.decode(preferredencoding())
835 assert isinstance(res, compat_str)
839 def write_string(s, out=None):
842 assert type(s) == type(u'')
844 if ('b' in getattr(out, 'mode', '') or
845 sys.version_info[0] < 3): # Python 2 lies about mode of sys.stderr
846 s = s.encode(preferredencoding(), 'ignore')
851 def bytes_to_intlist(bs):
854 if isinstance(bs[0], int): # Python 3
857 return [ord(c) for c in bs]
860 def intlist_to_bytes(xs):
863 if isinstance(chr(0), bytes): # Python 2
864 return ''.join([chr(x) for x in xs])
869 def get_cachedir(params={}):
870 cache_root = os.environ.get('XDG_CACHE_HOME',
871 os.path.expanduser('~/.cache'))
872 return params.get('cachedir', os.path.join(cache_root, 'youtube-dl'))
875 # Cross-platform file locking
876 if sys.platform == 'win32':
877 import ctypes.wintypes
880 class OVERLAPPED(ctypes.Structure):
882 ('Internal', ctypes.wintypes.LPVOID),
883 ('InternalHigh', ctypes.wintypes.LPVOID),
884 ('Offset', ctypes.wintypes.DWORD),
885 ('OffsetHigh', ctypes.wintypes.DWORD),
886 ('hEvent', ctypes.wintypes.HANDLE),
889 kernel32 = ctypes.windll.kernel32
890 LockFileEx = kernel32.LockFileEx
891 LockFileEx.argtypes = [
892 ctypes.wintypes.HANDLE, # hFile
893 ctypes.wintypes.DWORD, # dwFlags
894 ctypes.wintypes.DWORD, # dwReserved
895 ctypes.wintypes.DWORD, # nNumberOfBytesToLockLow
896 ctypes.wintypes.DWORD, # nNumberOfBytesToLockHigh
897 ctypes.POINTER(OVERLAPPED) # Overlapped
899 LockFileEx.restype = ctypes.wintypes.BOOL
900 UnlockFileEx = kernel32.UnlockFileEx
901 UnlockFileEx.argtypes = [
902 ctypes.wintypes.HANDLE, # hFile
903 ctypes.wintypes.DWORD, # dwReserved
904 ctypes.wintypes.DWORD, # nNumberOfBytesToLockLow
905 ctypes.wintypes.DWORD, # nNumberOfBytesToLockHigh
906 ctypes.POINTER(OVERLAPPED) # Overlapped
908 UnlockFileEx.restype = ctypes.wintypes.BOOL
909 whole_low = 0xffffffff
910 whole_high = 0x7fffffff
912 def _lock_file(f, exclusive):
913 overlapped = OVERLAPPED()
914 overlapped.Offset = 0
915 overlapped.OffsetHigh = 0
916 overlapped.hEvent = 0
917 f._lock_file_overlapped_p = ctypes.pointer(overlapped)
918 handle = msvcrt.get_osfhandle(f.fileno())
919 if not LockFileEx(handle, 0x2 if exclusive else 0x0, 0,
920 whole_low, whole_high, f._lock_file_overlapped_p):
921 raise OSError('Locking file failed: %r' % ctypes.FormatError())
924 assert f._lock_file_overlapped_p
925 handle = msvcrt.get_osfhandle(f.fileno())
926 if not UnlockFileEx(handle, 0,
927 whole_low, whole_high, f._lock_file_overlapped_p):
928 raise OSError('Unlocking file failed: %r' % ctypes.FormatError())
933 def _lock_file(f, exclusive):
934 fcntl.lockf(f, fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH)
937 fcntl.lockf(f, fcntl.LOCK_UN)
940 class locked_file(object):
941 def __init__(self, filename, mode, encoding=None):
942 assert mode in ['r', 'a', 'w']
943 self.f = io.open(filename, mode, encoding=encoding)
947 exclusive = self.mode != 'r'
949 _lock_file(self.f, exclusive)
955 def __exit__(self, etype, value, traceback):
964 def write(self, *args):
965 return self.f.write(*args)
967 def read(self, *args):
968 return self.f.read(*args)
971 def shell_quote(args):
973 encoding = sys.getfilesystemencoding()
977 if isinstance(a, bytes):
978 # We may get a filename encoded with 'encodeFilename'
979 a = a.decode(encoding)
980 quoted_args.append(pipes.quote(a))
981 return u' '.join(quoted_args)
984 def takewhile_inclusive(pred, seq):
985 """ Like itertools.takewhile, but include the latest evaluated element
986 (the first element so that Not pred(e)) """
993 def smuggle_url(url, data):
994 """ Pass additional data in a URL for internal use. """
996 sdata = compat_urllib_parse.urlencode(
997 {u'__youtubedl_smuggle': json.dumps(data)})
998 return url + u'#' + sdata
1001 def unsmuggle_url(smug_url):
1002 if not '#__youtubedl_smuggle' in smug_url:
1003 return smug_url, None
1004 url, _, sdata = smug_url.rpartition(u'#')
1005 jsond = compat_parse_qs(sdata)[u'__youtubedl_smuggle'][0]
1006 data = json.loads(jsond)