logo

oasis-root

Compiled tree of Oasis Linux based on own branch at <https://hacktivis.me/git/oasis/> git clone https://anongit.hacktivis.me/git/oasis-root.git

mhtml.py (5789B)


  1. import io
  2. import quopri
  3. import re
  4. import uuid
  5. from .fragment import FragmentFD
  6. from ..compat import imghdr
  7. from ..utils import escapeHTML, formatSeconds, srt_subtitles_timecode, urljoin
  8. from ..version import __version__ as YT_DLP_VERSION
  9. class MhtmlFD(FragmentFD):
  10. _STYLESHEET = '''\
  11. html, body {
  12. margin: 0;
  13. padding: 0;
  14. height: 100vh;
  15. }
  16. html {
  17. overflow-y: scroll;
  18. scroll-snap-type: y mandatory;
  19. }
  20. body {
  21. scroll-snap-type: y mandatory;
  22. display: flex;
  23. flex-flow: column;
  24. }
  25. body > figure {
  26. max-width: 100vw;
  27. max-height: 100vh;
  28. scroll-snap-align: center;
  29. }
  30. body > figure > figcaption {
  31. text-align: center;
  32. height: 2.5em;
  33. }
  34. body > figure > img {
  35. display: block;
  36. margin: auto;
  37. max-width: 100%;
  38. max-height: calc(100vh - 5em);
  39. }
  40. '''
  41. _STYLESHEET = re.sub(r'\s+', ' ', _STYLESHEET)
  42. _STYLESHEET = re.sub(r'\B \B|(?<=[\w\-]) (?=[^\w\-])|(?<=[^\w\-]) (?=[\w\-])', '', _STYLESHEET)
  43. @staticmethod
  44. def _escape_mime(s):
  45. return '=?utf-8?Q?' + (b''.join(
  46. bytes((b,)) if b >= 0x20 else b'=%02X' % b
  47. for b in quopri.encodestring(s.encode(), header=True)
  48. )).decode('us-ascii') + '?='
  49. def _gen_cid(self, i, fragment, frag_boundary):
  50. return f'{i}.{frag_boundary}@yt-dlp.github.io.invalid'
  51. def _gen_stub(self, *, fragments, frag_boundary, title):
  52. output = io.StringIO()
  53. output.write(
  54. '<!DOCTYPE html>'
  55. '<html>'
  56. '<head>'
  57. f'<meta name="generator" content="yt-dlp {escapeHTML(YT_DLP_VERSION)}">'
  58. f'<title>{escapeHTML(title)}</title>'
  59. f'<style>{self._STYLESHEET}</style>'
  60. '<body>')
  61. t0 = 0
  62. for i, frag in enumerate(fragments):
  63. output.write('<figure>')
  64. try:
  65. t1 = t0 + frag['duration']
  66. output.write((
  67. '<figcaption>Slide #{num}: {t0} – {t1} (duration: {duration})</figcaption>'
  68. ).format(
  69. num=i + 1,
  70. t0=srt_subtitles_timecode(t0),
  71. t1=srt_subtitles_timecode(t1),
  72. duration=formatSeconds(frag['duration'], msec=True),
  73. ))
  74. except (KeyError, ValueError, TypeError):
  75. t1 = None
  76. output.write(f'<figcaption>Slide #{i + 1}</figcaption>')
  77. output.write(f'<img src="cid:{self._gen_cid(i, frag, frag_boundary)}">')
  78. output.write('</figure>')
  79. t0 = t1
  80. return output.getvalue()
  81. def real_download(self, filename, info_dict):
  82. fragment_base_url = info_dict.get('fragment_base_url')
  83. fragments = info_dict['fragments'][:1] if self.params.get(
  84. 'test', False) else info_dict['fragments']
  85. title = info_dict.get('title', info_dict['format_id'])
  86. origin = info_dict.get('webpage_url', info_dict['url'])
  87. ctx = {
  88. 'filename': filename,
  89. 'total_frags': len(fragments),
  90. }
  91. self._prepare_and_start_frag_download(ctx, info_dict)
  92. extra_state = ctx.setdefault('extra_state', {
  93. 'header_written': False,
  94. 'mime_boundary': str(uuid.uuid4()).replace('-', ''),
  95. })
  96. frag_boundary = extra_state['mime_boundary']
  97. if not extra_state['header_written']:
  98. stub = self._gen_stub(
  99. fragments=fragments,
  100. frag_boundary=frag_boundary,
  101. title=title,
  102. )
  103. ctx['dest_stream'].write((
  104. 'MIME-Version: 1.0\r\n'
  105. 'From: <nowhere@yt-dlp.github.io.invalid>\r\n'
  106. 'To: <nowhere@yt-dlp.github.io.invalid>\r\n'
  107. f'Subject: {self._escape_mime(title)}\r\n'
  108. 'Content-type: multipart/related; '
  109. f'boundary="{frag_boundary}"; '
  110. 'type="text/html"\r\n'
  111. f'X.yt-dlp.Origin: {origin}\r\n'
  112. '\r\n'
  113. f'--{frag_boundary}\r\n'
  114. 'Content-Type: text/html; charset=utf-8\r\n'
  115. f'Content-Length: {len(stub)}\r\n'
  116. '\r\n'
  117. f'{stub}\r\n').encode())
  118. extra_state['header_written'] = True
  119. for i, fragment in enumerate(fragments):
  120. if (i + 1) <= ctx['fragment_index']:
  121. continue
  122. fragment_url = fragment.get('url')
  123. if not fragment_url:
  124. assert fragment_base_url
  125. fragment_url = urljoin(fragment_base_url, fragment['path'])
  126. success = self._download_fragment(ctx, fragment_url, info_dict)
  127. if not success:
  128. continue
  129. frag_content = self._read_fragment(ctx)
  130. frag_header = io.BytesIO()
  131. frag_header.write(
  132. b'--%b\r\n' % frag_boundary.encode('us-ascii'))
  133. frag_header.write(
  134. b'Content-ID: <%b>\r\n' % self._gen_cid(i, fragment, frag_boundary).encode('us-ascii'))
  135. frag_header.write(
  136. b'Content-type: %b\r\n' % f'image/{imghdr.what(h=frag_content) or "jpeg"}'.encode())
  137. frag_header.write(
  138. b'Content-length: %u\r\n' % len(frag_content))
  139. frag_header.write(
  140. b'Content-location: %b\r\n' % fragment_url.encode('us-ascii'))
  141. frag_header.write(
  142. b'X.yt-dlp.Duration: %f\r\n' % fragment['duration'])
  143. frag_header.write(b'\r\n')
  144. self._append_fragment(
  145. ctx, frag_header.getvalue() + frag_content + b'\r\n')
  146. ctx['dest_stream'].write(
  147. b'--%b--\r\n\r\n' % frag_boundary.encode('us-ascii'))
  148. return self._finish_frag_download(ctx, info_dict)