6 from .fragment
import FragmentFD
7 from ..compat
import imghdr
8 from ..utils
import escapeHTML
, formatSeconds
, srt_subtitles_timecode
, urljoin
9 from ..version
import __version__
as YT_DLP_VERSION
12 class MhtmlFD(FragmentFD
):
22 scroll-snap-type: y mandatory;
26 scroll-snap-type: y mandatory;
34 scroll-snap-align: center;
37 body > figure > figcaption {
46 max-height: calc(100vh - 5em);
49 _STYLESHEET
= re
.sub(r
'\s+', ' ', _STYLESHEET
)
50 _STYLESHEET
= re
.sub(r
'\B \B|(?<=[\w\-]) (?=[^\w\-])|(?<=[^\w\-]) (?=[\w\-])', '', _STYLESHEET
)
54 return '=?utf-8?Q?' + (b
''.join(
55 bytes((b
,)) if b
>= 0x20 else b
'=%02X' % b
56 for b
in quopri
.encodestring(s
.encode(), header
=True)
57 )).decode('us-ascii') + '?='
59 def _gen_cid(self
, i
, fragment
, frag_boundary
):
60 return f
'{i}.{frag_boundary}@yt-dlp.github.io.invalid'
62 def _gen_stub(self
, *, fragments
, frag_boundary
, title
):
63 output
= io
.StringIO()
69 f
'<meta name="generator" content="yt-dlp {escapeHTML(YT_DLP_VERSION)}">'
70 f
'<title>{escapeHTML(title)}</title>'
71 f
'<style>{self._STYLESHEET}</style>'
75 for i
, frag
in enumerate(fragments
):
76 output
.write('<figure>')
78 t1
= t0
+ frag
['duration']
80 '<figcaption>Slide #{num}: {t0} – {t1} (duration: {duration})</figcaption>'
83 t0
=srt_subtitles_timecode(t0
),
84 t1
=srt_subtitles_timecode(t1
),
85 duration
=formatSeconds(frag
['duration'], msec
=True),
87 except (KeyError, ValueError, TypeError):
89 output
.write(f
'<figcaption>Slide #{i + 1}</figcaption>')
90 output
.write(f
'<img src="cid:{self._gen_cid(i, frag, frag_boundary)}">')
91 output
.write('</figure>')
94 return output
.getvalue()
96 def real_download(self
, filename
, info_dict
):
97 fragment_base_url
= info_dict
.get('fragment_base_url')
98 fragments
= info_dict
['fragments'][:1] if self
.params
.get(
99 'test', False) else info_dict
['fragments']
100 title
= info_dict
.get('title', info_dict
['format_id'])
101 origin
= info_dict
.get('webpage_url', info_dict
['url'])
104 'filename': filename
,
105 'total_frags': len(fragments
),
108 self
._prepare
_and
_start
_frag
_download
(ctx
, info_dict
)
110 extra_state
= ctx
.setdefault('extra_state', {
111 'header_written': False,
112 'mime_boundary': str(uuid
.uuid4()).replace('-', ''),
115 frag_boundary
= extra_state
['mime_boundary']
117 if not extra_state
['header_written']:
118 stub
= self
._gen
_stub
(
120 frag_boundary
=frag_boundary
,
124 ctx
['dest_stream'].write((
125 'MIME-Version: 1.0\r\n'
126 'From: <nowhere@yt-dlp.github.io.invalid>\r\n'
127 'To: <nowhere@yt-dlp.github.io.invalid>\r\n'
128 f
'Subject: {self._escape_mime(title)}\r\n'
129 'Content-type: multipart/related; '
130 f
'boundary="{frag_boundary}"; '
131 'type="text/html"\r\n'
132 f
'X.yt-dlp.Origin: {origin}\r\n'
134 f
'--{frag_boundary}\r\n'
135 'Content-Type: text/html; charset=utf-8\r\n'
136 f
'Content-Length: {len(stub)}\r\n'
138 f
'{stub}\r\n').encode())
139 extra_state
['header_written'] = True
141 for i
, fragment
in enumerate(fragments
):
142 if (i
+ 1) <= ctx
['fragment_index']:
145 fragment_url
= fragment
.get('url')
147 assert fragment_base_url
148 fragment_url
= urljoin(fragment_base_url
, fragment
['path'])
150 success
= self
._download
_fragment
(ctx
, fragment_url
, info_dict
)
153 frag_content
= self
._read
_fragment
(ctx
)
155 frag_header
= io
.BytesIO()
157 b
'--%b\r\n' % frag_boundary
.encode('us-ascii'))
159 b
'Content-ID: <%b>\r\n' % self
._gen
_cid
(i
, fragment
, frag_boundary
).encode('us-ascii'))
161 b
'Content-type: %b\r\n' % f
'image/{imghdr.what(h=frag_content) or "jpeg"}'.encode())
163 b
'Content-length: %u\r\n' % len(frag_content
))
165 b
'Content-location: %b\r\n' % fragment_url
.encode('us-ascii'))
167 b
'X.yt-dlp.Duration: %f\r\n' % fragment
['duration'])
168 frag_header
.write(b
'\r\n')
169 self
._append
_fragment
(
170 ctx
, frag_header
.getvalue() + frag_content
+ b
'\r\n')
172 ctx
['dest_stream'].write(
173 b
'--%b--\r\n\r\n' % frag_boundary
.encode('us-ascii'))
174 return self
._finish
_frag
_download
(ctx
, info_dict
)