]> jfr.im git - yt-dlp.git/blob - yt_dlp/extractor/zype.py
[misc] Add `hatch`, `ruff`, `pre-commit` and improve dev docs (#7409)
[yt-dlp.git] / yt_dlp / extractor / zype.py
1 import re
2
3 from .common import InfoExtractor
4 from ..networking.exceptions import HTTPError
5 from ..utils import (
6 ExtractorError,
7 dict_get,
8 int_or_none,
9 js_to_json,
10 parse_iso8601,
11 )
12
13
14 class ZypeIE(InfoExtractor):
15 _ID_RE = r'[\da-fA-F]+'
16 _COMMON_RE = r'//player\.zype\.com/embed/%s\.(?:js|json|html)\?.*?(?:access_token|(?:ap[ip]|player)_key)='
17 _VALID_URL = r'https?:%s[^&]+' % (_COMMON_RE % ('(?P<id>%s)' % _ID_RE))
18 _EMBED_REGEX = [fr'<script[^>]+\bsrc=(["\'])(?P<url>(?:https?:)?{_COMMON_RE % _ID_RE}.+?)\1']
19 _TEST = {
20 'url': 'https://player.zype.com/embed/5b400b834b32992a310622b9.js?api_key=jZ9GUhRmxcPvX7M3SlfejB6Hle9jyHTdk2jVxG7wOHPLODgncEKVdPYBhuz9iWXQ&autoplay=false&controls=true&da=false',
21 'md5': 'eaee31d474c76a955bdaba02a505c595',
22 'info_dict': {
23 'id': '5b400b834b32992a310622b9',
24 'ext': 'mp4',
25 'title': 'Smoky Barbecue Favorites',
26 'thumbnail': r're:^https?://.*\.jpe?g',
27 'description': 'md5:5ff01e76316bd8d46508af26dc86023b',
28 'timestamp': 1504915200,
29 'upload_date': '20170909',
30 },
31 }
32
33 def _real_extract(self, url):
34 video_id = self._match_id(url)
35
36 try:
37 response = self._download_json(re.sub(
38 r'\.(?:js|html)\?', '.json?', url), video_id)['response']
39 except ExtractorError as e:
40 if isinstance(e.cause, HTTPError) and e.cause.status in (400, 401, 403):
41 raise ExtractorError(self._parse_json(
42 e.cause.response.read().decode(), video_id)['message'], expected=True)
43 raise
44
45 body = response['body']
46 video = response['video']
47 title = video['title']
48
49 subtitles = {}
50
51 if isinstance(body, dict):
52 formats = []
53 for output in body.get('outputs', []):
54 output_url = output.get('url')
55 if not output_url:
56 continue
57 name = output.get('name')
58 if name == 'm3u8':
59 formats, subtitles = self._extract_m3u8_formats_and_subtitles(
60 output_url, video_id, 'mp4',
61 'm3u8_native', m3u8_id='hls', fatal=False)
62 else:
63 f = {
64 'format_id': name,
65 'tbr': int_or_none(output.get('bitrate')),
66 'url': output_url,
67 }
68 if name in ('m4a', 'mp3'):
69 f['vcodec'] = 'none'
70 else:
71 f.update({
72 'height': int_or_none(output.get('height')),
73 'width': int_or_none(output.get('width')),
74 })
75 formats.append(f)
76 text_tracks = body.get('subtitles') or []
77 else:
78 m3u8_url = self._search_regex(
79 r'(["\'])(?P<url>(?:(?!\1).)+\.m3u8(?:(?!\1).)*)\1',
80 body, 'm3u8 url', group='url', default=None)
81 if not m3u8_url:
82 source = self._search_regex(
83 r'(?s)sources\s*:\s*\[\s*({.+?})\s*\]', body, 'source')
84
85 def get_attr(key):
86 return self._search_regex(
87 r'\b%s\s*:\s*([\'"])(?P<val>(?:(?!\1).)+)\1' % key,
88 source, key, group='val')
89
90 if get_attr('integration') == 'verizon-media':
91 m3u8_url = 'https://content.uplynk.com/%s.m3u8' % get_attr('id')
92 formats, subtitles = self._extract_m3u8_formats_and_subtitles(
93 m3u8_url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls')
94 text_tracks = self._search_regex(
95 r'textTracks\s*:\s*(\[[^]]+\])',
96 body, 'text tracks', default=None)
97 if text_tracks:
98 text_tracks = self._parse_json(
99 text_tracks, video_id, js_to_json, False)
100
101 if text_tracks:
102 for text_track in text_tracks:
103 tt_url = dict_get(text_track, ('file', 'src'))
104 if not tt_url:
105 continue
106 subtitles.setdefault(text_track.get('label') or 'English', []).append({
107 'url': tt_url,
108 })
109
110 thumbnails = []
111 for thumbnail in video.get('thumbnails', []):
112 thumbnail_url = thumbnail.get('url')
113 if not thumbnail_url:
114 continue
115 thumbnails.append({
116 'url': thumbnail_url,
117 'width': int_or_none(thumbnail.get('width')),
118 'height': int_or_none(thumbnail.get('height')),
119 })
120
121 return {
122 'id': video_id,
123 'display_id': video.get('friendly_title'),
124 'title': title,
125 'thumbnails': thumbnails,
126 'description': dict_get(video, ('description', 'ott_description', 'short_description')),
127 'timestamp': parse_iso8601(video.get('published_at')),
128 'duration': int_or_none(video.get('duration')),
129 'view_count': int_or_none(video.get('request_count')),
130 'average_rating': int_or_none(video.get('rating')),
131 'season_number': int_or_none(video.get('season')),
132 'episode_number': int_or_none(video.get('episode')),
133 'formats': formats,
134 'subtitles': subtitles,
135 }