# Copyright (c) 2022 The Regents of the University of Michigan
# All rights reserved.
# This software is licensed under the BSD 3-Clause License.
import glob
import itertools
import os
from flask import render_template
from signac_dashboard.module import Module
[docs]class VideoViewer(Module):
"""Displays videos that match a glob.
The VideoViewer module displays videos using an HTML ``<video>`` tag. The
module defaults to showing all videos of MP4 or M4V types in the job or
project directory. A filename or glob can be defined to select specific
filenames, which may be of any format supported by your browser with the
``<video>`` tag. Each matching file yields a card.
A "poster" can be defined, which shows a
thumbnail with that filename before the video is started. Videos do not
preload by default since file sizes can be large and there may be many
videos on a page. To enable preloading, use the argument ``preload='auto'``
or ``preload='metadata'``.
Multiple VideoViewer modules can be defined
with different filenames or globs to enable/disable cards individually.
:Example:
.. code-block:: python
from signac_dashboard.modules import VideoViewer
video_mod = VideoViewer() # Shows all MP4/M4V videos
video_mod = VideoViewer(name='Cool Science Video',
context="ProjectContext",
video_globs=['cool_science.mp4'],
poster='cool_science_thumbnail.jpg',
preload='none')
:param context: Supports :code:`'JobContext'` and :code:`'ProjectContext'`.
:type context: str
:param video_globs: A list of glob expressions or exact filenames,
relative to the job or project directory, to be displayed
(default: :code:`['*.mp4', '*.m4v']`).
:type video_globs: list
:param preload: Option for preloading videos, one of :code:`'auto'`,
:code:`'metadata'`, or :code:`'none'` (default: :code:`'none'`).
:type preload: str
:param poster: A path in the job directory or project directory for a
poster image to be shown before a video begins playback (default: :code:`None`).
:type poster: str
:type sort_key: callable
:param sort_key: Key to sort the video files, passed internally to :code:`sorted`.
"""
_supported_contexts = {"JobContext", "ProjectContext"}
def __init__(
self,
name="Video Viewer",
context="JobContext",
template="cards/video_viewer.html",
video_globs=("*.mp4", "*.m4v"),
preload="none", # auto|metadata|none
poster=None,
sort_key=None,
**kwargs,
):
super().__init__(
name=name,
context=context,
template=template,
**kwargs,
)
self.video_globs = video_globs
self.preload = preload
self.poster = poster
self.sort_key = sort_key
def get_cards(self, job_or_project):
if self.context == "JobContext":
jobid = job_or_project._id
elif self.context == "ProjectContext":
jobid = None
def make_card(filename):
if not job_or_project.isfile(filename):
raise FileNotFoundError(
"The filename {} could not be found "
"for {}.".format(
filename, "the project" if jobid is None else f"job {jobid}"
)
)
return {
"name": self.name + ": " + filename,
"content": render_template(
self.template,
jobid=jobid,
poster=self.poster
if self.poster and job_or_project.isfile(self.poster)
else None,
preload=self.preload,
filename=filename,
),
}
video_globs = [
glob.iglob(job_or_project.fn(video_glob)) for video_glob in self.video_globs
]
video_files = itertools.chain(*video_globs)
video_files = sorted(video_files, key=self.sort_key)
for filepath in video_files:
yield make_card(os.path.relpath(filepath, job_or_project.fn("")))