aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--yt_dlp/downloader/common.py6
-rw-r--r--yt_dlp/downloader/http.py3
-rw-r--r--yt_dlp/extractor/fptplay.py37
-rw-r--r--yt_dlp/utils.py95
4 files changed, 86 insertions, 55 deletions
diff --git a/yt_dlp/downloader/common.py b/yt_dlp/downloader/common.py
index afd2f2e38..cbfea7a65 100644
--- a/yt_dlp/downloader/common.py
+++ b/yt_dlp/downloader/common.py
@@ -11,6 +11,7 @@ from ..utils import (
encodeFilename,
error_to_compat_str,
format_bytes,
+ LockingUnsupportedError,
sanitize_open,
shell_quote,
timeconvert,
@@ -234,7 +235,10 @@ class FileDownloader(object):
@wrap_file_access('open', fatal=True)
def sanitize_open(self, filename, open_mode):
- return sanitize_open(filename, open_mode)
+ f, filename = sanitize_open(filename, open_mode)
+ if not getattr(f, 'locked', None):
+ self.write_debug(f'{LockingUnsupportedError.msg}. Proceeding without locking', only_once=True)
+ return f, filename
@wrap_file_access('remove')
def try_remove(self, filename):
diff --git a/yt_dlp/downloader/http.py b/yt_dlp/downloader/http.py
index 591a9b08d..a232168fa 100644
--- a/yt_dlp/downloader/http.py
+++ b/yt_dlp/downloader/http.py
@@ -145,7 +145,8 @@ class HttpFD(FileDownloader):
or content_len < range_end)
if accept_content_len:
ctx.content_len = content_len
- ctx.data_len = min(content_len, req_end or content_len) - (req_start or 0)
+ if content_len or req_end:
+ ctx.data_len = min(content_len or req_end, req_end or content_len) - (req_start or 0)
return
# Content-Range is either not present or invalid. Assuming remote webserver is
# trying to send the whole file, resume is not possible, so wiping the local file
diff --git a/yt_dlp/extractor/fptplay.py b/yt_dlp/extractor/fptplay.py
index a34e90bb1..c23fe6c53 100644
--- a/yt_dlp/extractor/fptplay.py
+++ b/yt_dlp/extractor/fptplay.py
@@ -7,12 +7,14 @@ import urllib.parse
from .common import InfoExtractor
from ..utils import (
+ clean_html,
join_nonempty,
+ strip_or_none,
)
class FptplayIE(InfoExtractor):
- _VALID_URL = r'https?://fptplay\.vn/(?P<type>xem-video)/[^/]+\-(?P<id>\w+)(?:/tap-(?P<episode>[^/]+)?/?(?:[?#]|$)|)'
+ _VALID_URL = r'https?://fptplay\.vn/xem-video/[^/]+\-(?P<id>\w+)(?:/tap-(?P<episode>\d+)?/?(?:[?#]|$)|)'
_GEO_COUNTRIES = ['VN']
IE_NAME = 'fptplay'
IE_DESC = 'fptplay.vn'
@@ -22,7 +24,7 @@ class FptplayIE(InfoExtractor):
'info_dict': {
'id': '621a123016f369ebbde55945',
'ext': 'mp4',
- 'title': 'Nhân Duyên Đại Nhân Xin Dừng Bước - Ms. Cupid In Love',
+ 'title': 'Nhân Duyên Đại Nhân Xin Dừng Bước - Tập 1A',
'description': 'md5:23cf7d1ce0ade8e21e76ae482e6a8c6c',
},
}, {
@@ -31,25 +33,42 @@ class FptplayIE(InfoExtractor):
'info_dict': {
'id': '61f3aa8a6b3b1d2e73c60eb5',
'ext': 'mp4',
- 'title': 'Má Tôi Là Đại Gia - 3',
+ 'title': 'Má Tôi Là Đại Gia - Tập 3',
'description': 'md5:ff8ba62fb6e98ef8875c42edff641d1c',
},
}, {
+ 'url': 'https://fptplay.vn/xem-video/lap-toi-do-giam-under-the-skin-6222d9684ec7230fa6e627a2/tap-4',
+ 'md5': 'bcb06c55ec14786d7d4eda07fa1ccbb9',
+ 'info_dict': {
+ 'id': '6222d9684ec7230fa6e627a2',
+ 'ext': 'mp4',
+ 'title': 'Lạp Tội Đồ Giám - Tập 2B',
+ 'description': 'md5:e5a47e9d35fbf7e9479ca8a77204908b',
+ },
+ }, {
'url': 'https://fptplay.vn/xem-video/nha-co-chuyen-hi-alls-well-ends-well-1997-6218995f6af792ee370459f0',
'only_matching': True,
}]
def _real_extract(self, url):
- type_url, video_id, episode = self._match_valid_url(url).group('type', 'id', 'episode')
- webpage = self._download_webpage(url, video_id=video_id, fatal=False)
- info = self._download_json(self.get_api_with_st_token(video_id, episode or 0), video_id)
+ video_id, slug_episode = self._match_valid_url(url).group('id', 'episode')
+ webpage = self._download_webpage(url, video_id=video_id, fatal=False) or ''
+ title = self._search_regex(
+ r'(?s)<h4\s+class="mb-1 text-2xl text-white"[^>]*>(.+)</h4>', webpage, 'title', fatal=False)
+ real_episode = slug_episode if not title else self._search_regex(
+ r'<p.+title="(?P<episode>[^">]+)"\s+class="epi-title active"', webpage, 'episode', fatal=False)
+ title = strip_or_none(title) or self._html_search_meta(('og:title', 'twitter:title'), webpage)
+
+ info = self._download_json(
+ self.get_api_with_st_token(video_id, int(slug_episode) - 1 if slug_episode else 0), video_id)
formats, subtitles = self._extract_m3u8_formats_and_subtitles(info['data']['url'], video_id, 'mp4')
self._sort_formats(formats)
return {
'id': video_id,
- 'title': join_nonempty(
- self._html_search_meta(('og:title', 'twitter:title'), webpage), episode, delim=' - '),
- 'description': self._html_search_meta(['og:description', 'twitter:description'], webpage),
+ 'title': join_nonempty(title, real_episode, delim=' - '),
+ 'description': (
+ clean_html(self._search_regex(r'<p\s+class="overflow-hidden"[^>]*>(.+)</p>', webpage, 'description'))
+ or self._html_search_meta(('og:description', 'twitter:description'), webpage)),
'formats': formats,
'subtitles': subtitles,
}
diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py
index 451aa18e1..d696bf3ab 100644
--- a/yt_dlp/utils.py
+++ b/yt_dlp/utils.py
@@ -674,26 +674,29 @@ def sanitize_open(filename, open_mode):
It returns the tuple (stream, definitive_file_name).
"""
- try:
- if filename == '-':
- if sys.platform == 'win32':
- import msvcrt
- msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
- return (sys.stdout.buffer if hasattr(sys.stdout, 'buffer') else sys.stdout, filename)
- stream = locked_file(filename, open_mode, block=False).open()
- return (stream, filename)
- except (IOError, OSError) as err:
- if err.errno in (errno.EACCES,):
- raise
+ if filename == '-':
+ if sys.platform == 'win32':
+ import msvcrt
+ msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
+ return (sys.stdout.buffer if hasattr(sys.stdout, 'buffer') else sys.stdout, filename)
- # In case of error, try to remove win32 forbidden chars
- alt_filename = sanitize_path(filename)
- if alt_filename == filename:
- raise
- else:
- # An exception here should be caught in the caller
- stream = locked_file(filename, open_mode, block=False).open()
- return (stream, alt_filename)
+ for attempt in range(2):
+ try:
+ try:
+ if sys.platform == 'win32':
+ # FIXME: Windows only has mandatory locking which also locks the file from being read.
+ # So for now, don't lock the file on windows. Ref: https://github.com/yt-dlp/yt-dlp/issues/3124
+ raise LockingUnsupportedError()
+ stream = locked_file(filename, open_mode, block=False).__enter__()
+ except LockingUnsupportedError:
+ stream = open(filename, open_mode)
+ return (stream, filename)
+ except (IOError, OSError) as err:
+ if attempt or err.errno in (errno.EACCES,):
+ raise
+ old_filename, filename = filename, sanitize_path(filename)
+ if old_filename == filename:
+ raise
def timeconvert(timestr):
@@ -2120,6 +2123,13 @@ def intlist_to_bytes(xs):
return compat_struct_pack('%dB' % len(xs), *xs)
+class LockingUnsupportedError(IOError):
+ msg = 'File locking is not supported on this platform'
+
+ def __init__(self):
+ super().__init__(self.msg)
+
+
# Cross-platform file locking
if sys.platform == 'win32':
import ctypes.wintypes
@@ -2200,21 +2210,20 @@ else:
fcntl.lockf(f, fcntl.LOCK_UN)
except ImportError:
- UNSUPPORTED_MSG = 'file locking is not supported on this platform'
def _lock_file(f, exclusive, block):
- raise IOError(UNSUPPORTED_MSG)
+ raise LockingUnsupportedError()
def _unlock_file(f):
- raise IOError(UNSUPPORTED_MSG)
+ raise LockingUnsupportedError()
class locked_file(object):
- _closed = False
+ locked = False
def __init__(self, filename, mode, block=True, encoding=None):
- assert mode in ['r', 'rb', 'a', 'ab', 'w', 'wb']
- self.f = io.open(filename, mode, encoding=encoding)
+ assert mode in {'r', 'rb', 'a', 'ab', 'w', 'wb'}
+ self.f = open(filename, mode, encoding=encoding)
self.mode = mode
self.block = block
@@ -2222,36 +2231,34 @@ class locked_file(object):
exclusive = 'r' not in self.mode
try:
_lock_file(self.f, exclusive, self.block)
+ self.locked = True
except IOError:
self.f.close()
raise
return self
- def __exit__(self, etype, value, traceback):
+ def unlock(self):
+ if not self.locked:
+ return
try:
- if not self._closed:
- _unlock_file(self.f)
+ _unlock_file(self.f)
finally:
- self.f.close()
- self._closed = True
-
- def __iter__(self):
- return iter(self.f)
-
- def write(self, *args):
- return self.f.write(*args)
+ self.locked = False
- def read(self, *args):
- return self.f.read(*args)
+ def __exit__(self, *_):
+ try:
+ self.unlock()
+ finally:
+ self.f.close()
- def flush(self):
- self.f.flush()
+ open = __enter__
+ close = __exit__
- def open(self):
- return self.__enter__()
+ def __getattr__(self, attr):
+ return getattr(self.f, attr)
- def close(self, *args):
- self.__exit__(self, *args, value=False, traceback=False)
+ def __iter__(self):
+ return iter(self.f)
def get_filesystem_encoding():