summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYves Fischer <yvesf-git@xapek.org>2015-12-31 17:35:15 +0100
committerYves Fischer <yvesf-git@xapek.org>2016-01-08 20:38:18 +0100
commit988f8cfc74446e7aa58f6e0ce916110e9ed23ca8 (patch)
treeb4239e1de001dec8cdbee8a1070de44b548bc373
parent8378a89bbd5b54d8efb064581725a765fb54740a (diff)
downloadflask-mediabrowser-988f8cfc74446e7aa58f6e0ce916110e9ed23ca8.tar.gz
flask-mediabrowser-988f8cfc74446e7aa58f6e0ce916110e9ed23ca8.zip
video thumbnails
-rw-r--r--mediabrowser/__init__.py16
-rw-r--r--mediabrowser/ffmpeg.py26
-rw-r--r--mediabrowser/templates/listdir.html25
3 files changed, 62 insertions, 5 deletions
diff --git a/mediabrowser/__init__.py b/mediabrowser/__init__.py
index 6927205..8766ac2 100644
--- a/mediabrowser/__init__.py
+++ b/mediabrowser/__init__.py
@@ -70,7 +70,7 @@ def build(root_directory, cache):
'.webm': 'video/webm',
'.flv': 'video/x-flv',
'.mp4': 'video/mp4',
- '.mpg': 'video/mp2t'}
+ '.mpg': 'video/MP2T'}
(filetype, encoding) = mimetypes.guess_type(path)
if filetype is None:
@@ -145,6 +145,20 @@ def build(root_directory, cache):
r.last_modified = mtime
return r
+ @blueprint.route('/<path:path>/thumbnail_video')
+ def thumbnail_video(path):
+ path = os.path.normpath(path)
+ ospath = os.path.join(root_directory, path)
+ client_mtime = request.if_modified_since
+ mtime = datetime.fromtimestamp(os.stat(ospath).st_mtime)
+ if client_mtime is not None and mtime <= client_mtime:
+ return Response(status=304)
+ else:
+ thumbnail_stream = ffmpeg.thumbnail_video(ospath, 90, 50)
+ r = Response(thumbnail_stream, mimetype="video/webm")
+ r.last_modified = mtime
+ return r
+
@blueprint.route('/<path:path>/download/inline')
def download_inline(path):
return download(path, inline=True)
diff --git a/mediabrowser/ffmpeg.py b/mediabrowser/ffmpeg.py
index ea589d6..b174b59 100644
--- a/mediabrowser/ffmpeg.py
+++ b/mediabrowser/ffmpeg.py
@@ -18,7 +18,7 @@ def LoggedPopen(command, *args, **kwargs):
def ffprobe_data(ospath):
logging.info('ffprobe %s', ospath)
- process = LoggedPopen(['ffprobe', '-v', 'quiet', '-print_format', 'json',
+ process = LoggedPopen(['ffprobe', '-v', 'fatal', '-print_format', 'json',
'-show_format', '-show_streams', ospath], stdout=PIPE, stderr=DEVNULL)
data = json.load(utf8reader(process.stdout))
assert process.wait() == 0, "ffprobe failed"
@@ -31,14 +31,14 @@ def stream(ospath, ss, t):
t_2 = t + 2.0
output_ts_offset = ss
cutter = LoggedPopen(
- shlex.split("ffmpeg -ss {ss:.6f} -i ".format(**locals())) +
+ shlex.split("ffmpeg -v fatal -ss {ss:.6f} -i ".format(**locals())) +
[ospath] +
shlex.split("-c:a aac -strict experimental -ac 2 -b:a 64k"
" -c:v libx264 -pix_fmt yuv420p -profile:v high -level 4.0 -preset ultrafast -trellis 0"
" -crf 31 -vf scale=w=trunc(oh*a/2)*2:h=480"
" -f mpegts"
" -output_ts_offset {output_ts_offset:.6f} -t {t:.6f} pipe:%d.ts".format(**locals())),
- stdout=PIPE, stderr=DEVNULL)
+ stdout=PIPE)
return cutter
@@ -121,3 +121,23 @@ def thumbnail(ospath, width, height):
" -f singlejpeg pipe:".format(height+(height/10), width, height)),
stdout=PIPE)
return process
+
+
+def thumbnail_video(ospath, width, height):
+ duration = float(ffprobe_data(ospath)['format']['duration'])
+
+ command = shlex.split("ffmpeg -v fatal")
+ chunk_startpos = range(min(int(duration)-1,30), int(duration), 500)
+ for pos in chunk_startpos:
+ command += ["-ss", "{:.6f}".format(pos), "-t", "2", "-i", ospath]
+
+ filter = " ".join(map(lambda i: "[{}:0]".format(i), range(len(chunk_startpos))))
+ filter += " concat=n={}:v=1:a=0 [v1]".format(len(chunk_startpos))
+ filter += "; [v1] fps=14 [v2]"
+ filter += "; [v2] scale='w=trunc(oh*a/2)*2:h={}' [v3]".format(height + 6)
+ filter += "; [v3] crop='min({},iw):min({},ih)' [v4]".format(width, height)
+ command += ['-filter_complex', filter, '-map', '[v4]']
+ command += shlex.split("-c:v libvpx -deadline realtime -f webm pipe:")
+
+ encoder = LoggedPopen(command, stdout=PIPE)
+ return encoder.stdout \ No newline at end of file
diff --git a/mediabrowser/templates/listdir.html b/mediabrowser/templates/listdir.html
index 008bb1e..cfa46f7 100644
--- a/mediabrowser/templates/listdir.html
+++ b/mediabrowser/templates/listdir.html
@@ -3,6 +3,28 @@
<head>
<title>Directory Browser - {{ path }}</title>
<link rel="stylesheet" href="{{ url_for('mediabrowser.assets', filename='style.css') }}" />
+ <script>
+ function convertOneThumbnailToVideo() {
+ var els = document.getElementsByClassName("thumbnail");
+ if (els.length > 0) {
+ var el = els[0];
+ console.log("convert",el);
+
+ var videoEl = document.createElement("video");
+ videoEl.addEventListener("error", convertOneThumbnailToVideo, true);
+ videoEl.addEventListener("canplaythrough", convertOneThumbnailToVideo, true);
+ videoEl.setAttribute("width", 90);
+ videoEl.setAttribute("height", 50);
+ videoEl.setAttribute("autoplay", "");
+ videoEl.setAttribute("loop", true);
+ videoEl.setAttribute("src", el.getAttribute("data-video-src"));
+ el.parentNode.replaceChild(videoEl, el);
+ }
+ }
+ window.onload = function() {
+ convertOneThumbnailToVideo();
+ }
+ </script>
</head>
<body class="list">
{% if parent != path %}
@@ -19,7 +41,8 @@
{% for file in files %}
{% if file['type'] == 'file' %}
<div>
- <img src="{{ url_for('mediabrowser.thumbnail', path=file['fullpath']) }}" />
+ <img class="thumbnail" src="{{ url_for('mediabrowser.thumbnail', path=file['fullpath']) }}"
+ data-video-src="{{ url_for('mediabrowser.thumbnail_video', path=file['fullpath']) }}"/>
<a href="{{ url_for('mediabrowser.watch', path=file['fullpath']) }}">
{{ file['filename'] }}
</a>