# -*- coding: utf-8 -*-
"""
Defines a sphinx extension to add button to share a page
:githublink:`%|py|6`
"""
import os
import copy
import shutil
import sphinx
from docutils import nodes
from docutils.parsers.rst import Directive, directives
from sphinx.util.logging import getLogger
from sphinx.util import FilenameUniqDict
DEFAULT_CONFIG = dict(
default_video_width='100%',
default_video_height='auto',
cache_path='_videos',
)
[docs]class video_node(nodes.General, nodes.Element):
"""
Defines *video* node.
:githublink:`%|py|27`
"""
pass
[docs]class VideoDirective(Directive):
"""
Adds video to a page. It can be done by adding::
.. video:: filename.mp4
:width: 400
:height: 600
For latex, unit becomes *pt*.
See `latex units <https://tex.stackexchange.com/questions/8260/what-are-the-various-units-ex-em-in-pt-bp-dd-pc-expressed-in-mm>`_.
Videos are not enabled on latex by default,
option ``:latex:`` must be set up.
:githublink:`%|py|43`
"""
required_arguments = True
optional_arguments = 0
final_argument_whitespace = True
option_spec = {'width': directives.unchanged,
'height': directives.unchanged,
'latex': directives.unchanged,
}
has_content = True
video_class = video_node
[docs] def run(self):
"""
Runs the directive.
:return: a list of nodes
:githublink:`%|py|59`
"""
env = self.state.document.settings.env
conf = env.app.config.videos_config
docname = None if env is None else env.docname
if docname is not None:
docname = docname.replace("\\", "/").split("/")[-1]
else:
docname = ''
source = self.state.document.current_source
filename = self.arguments[0]
if '://' in filename:
logger = getLogger("video")
logger.warning(
"[video] url detected '{0}' in docname '{1}' - line {2}.".format(filename, docname, self.lineno))
is_url = True
else:
is_url = False
if not is_url:
env.videos.add_file('', filename)
srcdir = env.srcdir
rstrel = os.path.relpath(source, srcdir)
rstfold = os.path.split(rstrel)[0]
cache = os.path.join(srcdir, conf['cache_path'])
vid = os.path.join(cache, filename)
abspath = None
relpath = None
if os.path.exists(vid):
abspath = vid
relpath = cache
else:
last = rstfold.replace('\\', '/')
vid = os.path.join(srcdir, last, filename)
if os.path.exists(vid):
relpath = last
abspath = vid
if abspath is None:
logger = getLogger("video")
logger.warning(
"[video] Unable to find '{0}' in docname '{1}' - line {2} - srcdir='{3}'.".format(filename, docname, self.lineno, srcdir))
else:
abspath = None
relpath = None
width = self.options.get('width', conf['default_video_width'])
height = self.options.get('height', conf['default_video_height'])
latex = self.options.get('latex', False) in (
'True', 'true', True, 1, "1")
# build node
node = self.__class__.video_class(uri=filename, docname=docname, lineno=self.lineno,
width=width, height=height, abspath=abspath,
relpath=relpath, is_url=is_url)
node['classes'] += ["place-video"]
node['video'] = filename
node['latex'] = latex
ns = [node]
return ns
[docs]def visit_video_node(self, node):
"""
Visits a video node.
Copies the video.
:githublink:`%|py|128`
"""
if node['abspath'] is not None:
outdir = self.builder.outdir
relpath = os.path.join(outdir, node['relpath'])
dname = os.path.split(node['uri'])[0]
if dname:
relpath = os.path.join(relpath, dname)
if not os.path.exists(relpath):
os.makedirs(relpath)
shutil.copy(node['abspath'], relpath)
logger = getLogger("video")
logger.info("[video] copy '{0}' to '{1}'".format(node['uri'], relpath))
[docs]def _clean_value(val):
if isinstance(val, tuple):
return val[0]
else:
return val
[docs]def depart_video_node_html(self, node):
"""
What to do when leaving a node *video*
the function should have different behaviour,
depending on the format, or the setup should
specify a different function for each.
:githublink:`%|py|155`
"""
if node.hasattr("uri"):
filename = node["uri"]
width = _clean_value(node["width"])
height = _clean_value(node["height"])
found = node["abspath"] is not None or node["is_url"]
if not found:
body = "<b>unable to find '{0}'</b>".format(filename)
self.body.append(body)
else:
body = '<video{0}{1} controls><source src="{2}" type="video/{3}">Your browser does not support the video tag.</video>'
width = ' width="{0}"'.format(width) if width else ""
height = ' height="{0}"'.format(height) if height else ""
body = body.format(width, height, filename,
os.path.splitext(filename)[-1].strip('.'))
self.body.append(body)
[docs]def depart_video_node_text(self, node):
"""
What to do when leaving a node *video*
the function should have different behaviour,
depending on the format, or the setup should
specify a different function for each.
:githublink:`%|py|179`
"""
if 'rst' in (self.builder.name, self.builder.format):
depart_video_node_rst(self, node)
elif 'latex' in (self.builder.name, self.builder.format):
depart_video_node_latex(self, node)
elif node.hasattr("uri"):
filename = node["uri"]
width = _clean_value(node["width"])
height = _clean_value(node["height"])
found = node["abspath"] is not None or node["is_url"]
if not found:
body = "unable to find '{0}'".format(filename)
self.body.append(body)
else:
body = '\nvideo {0}{1}: {2}\n'
width = ' width="{0}"'.format(width) if width else ""
height = ' height="{0}"'.format(height) if height else ""
body = body.format(width, height, filename,
os.path.splitext(filename)[-1].strip('.'))
self.add_text(body)
[docs]def depart_video_node_latex(self, node):
"""
What to do when leaving a node *video*
the function should have different behaviour,
depending on the format, or the setup should
specify a different function for each.
:githublink:`%|py|207`
"""
if node.hasattr("uri"):
width = _clean_value(node["width"])
height = _clean_value(node["height"])
full = os.path.join(node["relpath"], node['uri'])
found = node['abspath'] is not None or node["is_url"]
if not found:
body = "\\textbf{{unable to find '{0}'}}".format(full)
self.body.append(body)
else:
def format_dim(s):
"local function"
if s == "auto" or s is None:
return "{}"
else:
return "{{{0}pt}}".format(s)
body = '{3}\\includemovie[poster,autoplay,externalviewer,inline=false]{0}{1}{{{2}}}\n'
width = format_dim(width)
height = format_dim(height)
full = full.replace('\\', '/').replace('_', '\\_')
comment = '' if node['latex'] else '%'
body = body.format(width, height, full, comment)
self.body.append(body)
[docs]def depart_video_node_rst(self, node):
"""
What to do when leaving a node *video*
the function should have different behaviour,
depending on the format, or the setup should
specify a different function for each.
:githublink:`%|py|238`
"""
if node.hasattr("uri"):
filename = node["uri"]
width = _clean_value(node["width"])
height = _clean_value(node["height"])
found = node["abspath"] is not None or node["is_url"]
if not found:
body = ".. video:: {0} [not found]".format(filename)
self.add_text(body + self.nl)
else:
body = ".. video:: {0}".format(filename)
self.new_state(0)
self.add_text(body + self.nl)
if width:
self.add_text(' :width: {0}'.format(width) + self.nl)
if height:
self.add_text(' :height: {0}'.format(height) + self.nl)
self.end_state(wrap=False)
[docs]def initialize_videos_directive(app):
"""
Initializes the video directives.
:githublink:`%|py|261`
"""
global DEFAULT_CONFIG
config = copy.deepcopy(DEFAULT_CONFIG)
config.update(app.config.videos_config)
app.config.videos_config = config
app.env.videos = FilenameUniqDict()
[docs]def setup(app):
"""
setup for ``video`` (sphinx)
:githublink:`%|py|273`
"""
global DEFAULT_CONFIG
if hasattr(app, "add_mapping"):
app.add_mapping('video', video_node)
app.add_config_value('videos_config', DEFAULT_CONFIG, 'env')
app.connect('builder-inited', initialize_videos_directive)
app.add_node(video_node,
html=(visit_video_node, depart_video_node_html),
epub=(visit_video_node, depart_video_node_html),
elatex=(visit_video_node, depart_video_node_latex),
latex=(visit_video_node, depart_video_node_latex),
rst=(visit_video_node, depart_video_node_rst),
md=(visit_video_node, depart_video_node_rst),
text=(visit_video_node, depart_video_node_text))
app.add_directive('video', VideoDirective)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}