diff options
author | Yves Fischer <yvesf-git@xapek.org> | 2015-12-31 17:35:15 +0100 |
---|---|---|
committer | Yves Fischer <yvesf-git@xapek.org> | 2016-01-08 20:38:18 +0100 |
commit | 988f8cfc74446e7aa58f6e0ce916110e9ed23ca8 (patch) | |
tree | b4239e1de001dec8cdbee8a1070de44b548bc373 | |
parent | 8378a89bbd5b54d8efb064581725a765fb54740a (diff) | |
download | flask-mediabrowser-988f8cfc74446e7aa58f6e0ce916110e9ed23ca8.tar.gz flask-mediabrowser-988f8cfc74446e7aa58f6e0ce916110e9ed23ca8.zip |
video thumbnails
-rw-r--r-- | mediabrowser/__init__.py | 16 | ||||
-rw-r--r-- | mediabrowser/ffmpeg.py | 26 | ||||
-rw-r--r-- | mediabrowser/templates/listdir.html | 25 |
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> |