youtube.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Taken from sphinx-contrib
  4. # https://bitbucket.org/birkenfeld/sphinx-contrib/src/a3d904f8ab24/youtube
  5. # If not otherwise noted, the extensions in this package are licensed
  6. # under the following license.
  7. #
  8. # Copyright (c) 2009 by the contributors (see AUTHORS file).
  9. # All rights reserved.
  10. #
  11. # Redistribution and use in source and binary forms, with or without
  12. # modification, are permitted provided that the following conditions are
  13. # met:
  14. #
  15. # * Redistributions of source code must retain the above copyright
  16. # notice, this list of conditions and the following disclaimer.
  17. #
  18. # * Redistributions in binary form must reproduce the above copyright
  19. # notice, this list of conditions and the following disclaimer in the
  20. # documentation and/or other materials provided with the distribution.
  21. #
  22. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  23. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  24. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  25. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  26. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  27. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  28. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  30. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  31. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  32. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33. from __future__ import division
  34. import re
  35. from docutils import nodes
  36. from docutils.parsers.rst import directives
  37. try:
  38. from sphinx.util.compat import Directive
  39. except ImportError:
  40. from docutils.parsers.rst import Directive
  41. CONTROL_HEIGHT = 30
  42. def get_size(d, key):
  43. if key not in d:
  44. return None
  45. m = re.match("(\d+)(|%|px)$", d[key])
  46. if not m:
  47. raise ValueError("invalid size %r" % d[key])
  48. return int(m.group(1)), m.group(2) or "px"
  49. def css(d):
  50. return "; ".join(sorted("%s: %s" % kv for kv in d.iteritems()))
  51. class youtube(nodes.General, nodes.Element):
  52. pass
  53. def visit_youtube_node(self, node):
  54. aspect = node["aspect"]
  55. width = node["width"]
  56. height = node["height"]
  57. if aspect is None:
  58. aspect = 16, 9
  59. if (height is None) and (width is not None) and (width[1] == "%"):
  60. style = {
  61. "padding-top": "%dpx" % CONTROL_HEIGHT,
  62. "padding-bottom": "%f%%" % (width[0] * aspect[1] / aspect[0]),
  63. "width": "%d%s" % width,
  64. "position": "relative",
  65. }
  66. self.body.append(self.starttag(node, "div", style=css(style)))
  67. style = {
  68. "position": "absolute",
  69. "top": "0",
  70. "left": "0",
  71. "width": "100%",
  72. "height": "100%",
  73. "border": "0",
  74. }
  75. attrs = {
  76. "src": "http://www.youtube.com/embed/%s" % node["id"],
  77. "style": css(style),
  78. }
  79. self.body.append(self.starttag(node, "iframe", **attrs))
  80. self.body.append("</iframe></div>")
  81. else:
  82. if width is None:
  83. if height is None:
  84. width = 560, "px"
  85. else:
  86. width = height[0] * aspect[0] / aspect[1], "px"
  87. if height is None:
  88. height = width[0] * aspect[1] / aspect[0], "px"
  89. style = {
  90. "width": "%d%s" % width,
  91. "height": "%d%s" % (height[0] + CONTROL_HEIGHT, height[1]),
  92. "border": "0",
  93. }
  94. attrs = {
  95. "src": "http://www.youtube.com/embed/%s" % node["id"],
  96. "style": css(style),
  97. }
  98. self.body.append(self.starttag(node, "iframe", **attrs))
  99. self.body.append("</iframe>")
  100. def depart_youtube_node(self, node):
  101. pass
  102. class YouTube(Directive):
  103. has_content = True
  104. required_arguments = 1
  105. optional_arguments = 0
  106. final_argument_whitespace = False
  107. option_spec = {
  108. "width": directives.unchanged,
  109. "height": directives.unchanged,
  110. "aspect": directives.unchanged,
  111. }
  112. def run(self):
  113. if "aspect" in self.options:
  114. aspect = self.options.get("aspect")
  115. m = re.match("(\d+):(\d+)", aspect)
  116. if m is None:
  117. raise ValueError("invalid aspect ratio %r" % aspect)
  118. aspect = tuple(int(x) for x in m.groups())
  119. else:
  120. aspect = None
  121. width = get_size(self.options, "width")
  122. height = get_size(self.options, "height")
  123. return [
  124. youtube(id=self.arguments[0], aspect=aspect, width=width, height=height)
  125. ]
  126. def setup(app):
  127. app.add_node(youtube, html=(visit_youtube_node, depart_youtube_node))
  128. app.add_directive("youtube", YouTube)