aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--yt_dlp/extractor/manyvids.py180
1 files changed, 63 insertions, 117 deletions
diff --git a/yt_dlp/extractor/manyvids.py b/yt_dlp/extractor/manyvids.py
index 8caa8f87f..1356169bf 100644
--- a/yt_dlp/extractor/manyvids.py
+++ b/yt_dlp/extractor/manyvids.py
@@ -1,31 +1,38 @@
-import re
-
from .common import InfoExtractor
from ..utils import (
+ clean_html,
determine_ext,
- extract_attributes,
int_or_none,
- str_to_int,
+ join_nonempty,
+ parse_count,
+ parse_duration,
+ parse_iso8601,
url_or_none,
- urlencode_postdata,
)
+from ..utils.traversal import traverse_obj
class ManyVidsIE(InfoExtractor):
- _WORKING = False
_VALID_URL = r'(?i)https?://(?:www\.)?manyvids\.com/video/(?P<id>\d+)'
_TESTS = [{
# preview video
- 'url': 'https://www.manyvids.com/Video/133957/everthing-about-me/',
- 'md5': '03f11bb21c52dd12a05be21a5c7dcc97',
+ 'url': 'https://www.manyvids.com/Video/530341/mv-tips-tricks',
+ 'md5': '738dc723f7735ee9602f7ea352a6d058',
'info_dict': {
- 'id': '133957',
+ 'id': '530341-preview',
'ext': 'mp4',
- 'title': 'everthing about me (Preview)',
- 'uploader': 'ellyxxix',
+ 'title': 'MV Tips & Tricks (Preview)',
+ 'description': r're:I will take you on a tour around .{1313}$',
+ 'thumbnail': r're:https://cdn5\.manyvids\.com/php_uploads/video_images/DestinyDiaz/.+\.jpg',
+ 'uploader': 'DestinyDiaz',
'view_count': int,
'like_count': int,
+ 'release_timestamp': 1508419904,
+ 'tags': ['AdultSchool', 'BBW', 'SFW', 'TeacherFetish'],
+ 'release_date': '20171019',
+ 'duration': 3167.0,
},
+ 'expected_warnings': ['Only extracting preview'],
}, {
# full video
'url': 'https://www.manyvids.com/Video/935718/MY-FACE-REVEAL/',
@@ -34,129 +41,68 @@ class ManyVidsIE(InfoExtractor):
'id': '935718',
'ext': 'mp4',
'title': 'MY FACE REVEAL',
- 'description': 'md5:ec5901d41808b3746fed90face161612',
+ 'description': r're:Today is the day!! I am finally taking off my mask .{445}$',
+ 'thumbnail': r're:https://ods\.manyvids\.com/1001061960/3aa5397f2a723ec4597e344df66ab845/screenshots/.+\.jpg',
'uploader': 'Sarah Calanthe',
'view_count': int,
'like_count': int,
+ 'release_date': '20181110',
+ 'tags': ['EyeContact', 'Interviews', 'MaskFetish', 'MouthFetish', 'Redhead'],
+ 'release_timestamp': 1541851200,
+ 'duration': 224.0,
},
}]
+ _API_BASE = 'https://www.manyvids.com/bff/store/video'
def _real_extract(self, url):
video_id = self._match_id(url)
-
- real_url = f'https://www.manyvids.com/video/{video_id}/gtm.js'
- try:
- webpage = self._download_webpage(real_url, video_id)
- except Exception:
- # probably useless fallback
- webpage = self._download_webpage(url, video_id)
-
- info = self._search_regex(
- r'''(<div\b[^>]*\bid\s*=\s*(['"])pageMetaDetails\2[^>]*>)''',
- webpage, 'meta details', default='')
- info = extract_attributes(info)
-
- player = self._search_regex(
- r'''(<div\b[^>]*\bid\s*=\s*(['"])rmpPlayerStream\2[^>]*>)''',
- webpage, 'player details', default='')
- player = extract_attributes(player)
-
- video_urls_and_ids = (
- (info.get('data-meta-video'), 'video'),
- (player.get('data-video-transcoded'), 'transcoded'),
- (player.get('data-video-filepath'), 'filepath'),
- (self._og_search_video_url(webpage, secure=False, default=None), 'og_video'),
- )
-
- def txt_or_none(s, default=None):
- return (s.strip() or default) if isinstance(s, str) else default
-
- uploader = txt_or_none(info.get('data-meta-author'))
-
- def mung_title(s):
- if uploader:
- s = re.sub(rf'^\s*{re.escape(uploader)}\s+[|-]', '', s)
- return txt_or_none(s)
-
- title = (
- mung_title(info.get('data-meta-title'))
- or self._html_search_regex(
- (r'<span[^>]+class=["\']item-title[^>]+>([^<]+)',
- r'<h2[^>]+class=["\']h2 m-0["\'][^>]*>([^<]+)'),
- webpage, 'title', default=None)
- or self._html_search_meta(
- 'twitter:title', webpage, 'title', fatal=True))
-
- title = re.sub(r'\s*[|-]\s+ManyVids\s*$', '', title) or title
-
- if any(p in webpage for p in ('preview_videos', '_preview.mp4')):
- title += ' (Preview)'
-
- mv_token = self._search_regex(
- r'data-mvtoken=(["\'])(?P<value>(?:(?!\1).)+)\1', webpage,
- 'mv token', default=None, group='value')
-
- if mv_token:
- # Sets some cookies
- self._download_webpage(
- 'https://www.manyvids.com/includes/ajax_repository/you_had_me_at_hello.php',
- video_id, note='Setting format cookies', fatal=False,
- data=urlencode_postdata({
- 'mvtoken': mv_token,
- 'vid': video_id,
- }), headers={
- 'Referer': url,
- 'X-Requested-With': 'XMLHttpRequest',
- })
-
- formats = []
- for v_url, fmt in video_urls_and_ids:
- v_url = url_or_none(v_url)
- if not v_url:
+ video_data = self._download_json(f'{self._API_BASE}/{video_id}/private', video_id)['data']
+ formats, preview_only = [], True
+
+ for format_id, path in [
+ ('preview', ['teaser', 'filepath']),
+ ('transcoded', ['transcodedFilepath']),
+ ('filepath', ['filepath']),
+ ]:
+ format_url = traverse_obj(video_data, (*path, {url_or_none}))
+ if not format_url:
continue
- if determine_ext(v_url) == 'm3u8':
- formats.extend(self._extract_m3u8_formats(
- v_url, video_id, 'mp4', entry_protocol='m3u8_native',
- m3u8_id='hls'))
+ if determine_ext(format_url) == 'm3u8':
+ formats.extend(self._extract_m3u8_formats(format_url, video_id, 'mp4', m3u8_id=format_id))
else:
formats.append({
- 'url': v_url,
- 'format_id': fmt,
+ 'url': format_url,
+ 'format_id': format_id,
+ 'preference': -10 if format_id == 'preview' else None,
+ 'quality': 10 if format_id == 'filepath' else None,
+ 'height': int_or_none(
+ self._search_regex(r'_(\d{2,3}[02468])_', format_url, 'height', default=None)),
})
+ if format_id != 'preview':
+ preview_only = False
- self._remove_duplicate_formats(formats)
-
- for f in formats:
- if f.get('height') is None:
- f['height'] = int_or_none(
- self._search_regex(r'_(\d{2,3}[02468])_', f['url'], 'video height', default=None))
- if '/preview/' in f['url']:
- f['format_id'] = '_'.join(filter(None, (f.get('format_id'), 'preview')))
- f['preference'] = -10
- if 'transcoded' in f['format_id']:
- f['preference'] = f.get('preference', -1) - 1
-
- def get_likes():
- likes = self._search_regex(
- rf'''(<a\b[^>]*\bdata-id\s*=\s*(['"]){video_id}\2[^>]*>)''',
- webpage, 'likes', default='')
- likes = extract_attributes(likes)
- return int_or_none(likes.get('data-likes'))
+ metadata = traverse_obj(
+ self._download_json(f'{self._API_BASE}/{video_id}', video_id, fatal=False), 'data')
+ title = traverse_obj(metadata, ('title', {clean_html}))
- def get_views():
- return str_to_int(self._html_search_regex(
- r'''(?s)<span\b[^>]*\bclass\s*=["']views-wrapper\b[^>]+>.+?<span\b[^>]+>\s*(\d[\d,.]*)\s*</span>''',
- webpage, 'view count', default=None))
+ if preview_only:
+ title = join_nonempty(title, '(Preview)', delim=' ')
+ video_id += '-preview'
+ self.report_warning(
+ f'Only extracting preview. Video may be paid or subscription only. {self._login_hint()}')
return {
'id': video_id,
'title': title,
'formats': formats,
- 'description': txt_or_none(info.get('data-meta-description')),
- 'uploader': txt_or_none(info.get('data-meta-author')),
- 'thumbnail': (
- url_or_none(info.get('data-meta-image'))
- or url_or_none(player.get('data-video-screenshot'))),
- 'view_count': get_views(),
- 'like_count': get_likes(),
+ **traverse_obj(metadata, {
+ 'description': ('description', {clean_html}),
+ 'uploader': ('model', 'displayName', {clean_html}),
+ 'thumbnail': (('screenshot', 'thumbnail'), {url_or_none}, any),
+ 'view_count': ('views', {parse_count}),
+ 'like_count': ('likes', {parse_count}),
+ 'release_timestamp': ('launchDate', {parse_iso8601}),
+ 'duration': ('videoDuration', {parse_duration}),
+ 'tags': ('tagList', ..., 'label', {str}, filter, all, filter),
+ }),
}