Adding video support

This commit is contained in:
Christophe-Marie Duquesne
2013-07-17 00:43:37 +02:00
parent e085691cda
commit 0b5e38b17c
7 changed files with 134 additions and 34 deletions

View File

@@ -34,7 +34,8 @@ from clint.textui import progress, colored
from os.path import join
from PIL import Image as PILImage
from .image import generate_image, generate_thumbnail
import sigal.image
import sigal.video
from .settings import get_thumb
from .writer import Writer
@@ -49,8 +50,9 @@ class PathsDb(object):
"""
def __init__(self, path, ext_list):
self.ext_list = ext_list
def __init__(self, path, img_ext_list, vid_ext_list):
self.img_ext_list = img_ext_list
self.vid_ext_list = vid_ext_list
self.logger = logging.getLogger(__name__)
# basepath must to be a unicode string so that os.walk will return
@@ -93,22 +95,26 @@ class PathsDb(object):
self.db['paths_list'].append(relpath)
self.db[relpath] = {
'img': [f for f in filenames
if os.path.splitext(f)[1] in self.ext_list],
if os.path.splitext(f)[1] in self.img_ext_list],
'vid': [f for f in filenames
if os.path.splitext(f)[1] in self.vid_ext_list],
'subdir': dirnames
}
self.db[relpath].update(get_metadata(path))
path_im = [path for path in self.db['paths_list']
if self.db[path]['img'] and path != '.']
path_noim = [path for path in self.db['paths_list']
if not self.db[path]['img'] and path != '.']
path_media = [path for path in self.db['paths_list']
if (self.db[path]['img'] or self.db[path]['vid']) and
path != '.']
path_nomedia = [path for path in self.db['paths_list'] if not
(self.db[path]['img'] or self.db[path]['vid']) and path !=
'.']
# dir with images: check the thumbnail, and find it if necessary
for path in path_im:
for path in path_media:
self.check_thumbnail(path)
# dir without images, start with the deepest ones
for path in reversed(sorted(path_noim, key=lambda x: x.count('/'))):
for path in reversed(sorted(path_nomedia, key=lambda x: x.count('/'))):
for subdir in self.get_subdirs(path):
# use the thumbnail of their sub-directories
if self.db[subdir].get('thumbnail', ''):
@@ -162,7 +168,8 @@ class Gallery(object):
theme=theme)
self.paths = PathsDb(self.settings['source'],
self.settings['ext_list'])
self.settings['img_ext_list'],
self.settings['vid_ext_list'])
self.paths.build()
self.db = self.paths.db
@@ -181,20 +188,22 @@ class Gallery(object):
for path in reversed(self.db['paths_list']):
imglist = [os.path.normpath(join(self.settings['source'], path, f))
for f in self.db[path]['img']]
vidlist = [os.path.normpath(join(self.settings['source'], path, f))
for f in self.db[path]['vid']]
# output dir for the current path
img_out = os.path.normpath(join(self.settings['destination'],
outpath = os.path.normpath(join(self.settings['destination'],
path))
check_or_create_dir(img_out)
check_or_create_dir(outpath)
if len(imglist) != 0:
self.process_dir(imglist, img_out, path,
if len(imglist) != 0 or len(vidlist) != 0:
self.process_dir(imglist, vidlist, outpath, path,
label_width=label_width)
if self.settings['write_html']:
self.writer.write(self.db, path)
def process_dir(self, imglist, outpath, dirname, label_width=20):
def process_dir(self, imglist, vidlist, outpath, dirname, label_width=20):
"""Process a list of images in a directory."""
# Create thumbnails directory and optionally the one for original img
@@ -203,28 +212,36 @@ class Gallery(object):
if self.settings['keep_orig']:
check_or_create_dir(join(outpath, self.settings['orig_dir']))
media_files = imglist + vidlist
# use progressbar if level is > INFO
if self.logger.getEffectiveLevel() > 20:
label = colored.green(dirname.ljust(label_width))
img_iterator = progress.bar(imglist, label=label)
media_iterator = progress.bar(media_files, label=label)
else:
img_iterator = iter(imglist)
media_iterator = iter(media_files)
self.logger.info("")
self.logger.info(":: Processing '%s' [%i images]",
colored.green(dirname), len(imglist))
self.logger.info(":: Processing '%s' [%i img/vid]",
colored.green(dirname), len(media_files))
self.logger.info("")
try:
# loop on images
for f in img_iterator:
for f in media_iterator:
filename = os.path.split(f)[1]
outname = join(outpath, filename)
if f in imglist:
outname = join(outpath, filename)
else:
outname = ''.join([os.path.splitext(filename)[0], '.webm'])
if os.path.isfile(outname) and not self.force:
self.logger.info("%s exists - skipping", filename)
else:
self.logger.info(filename)
process_image(f, outpath, self.settings)
if f in imglist:
process_image(f, outpath, self.settings)
else:
process_video(f, outpath, self.settings)
except KeyboardInterrupt:
sys.exit('Interrupted')
@@ -247,15 +264,31 @@ def process_image(filepath, outpath, settings):
if settings['keep_orig']:
shutil.copy(filepath, join(outpath, settings['orig_dir'], filename))
generate_image(filepath, outname, settings['img_size'], None,
sigal.image.generate_image(filepath, outname, settings['img_size'], None,
options=options, copyright_text=settings['copyright'],
method=settings['img_processor'])
if settings['make_thumbs']:
thumb_name = join(outpath, get_thumb(settings, filename))
generate_thumbnail(outname, thumb_name, settings['thumb_size'], None,
sigal.image.generate_thumbnail(outname, thumb_name, settings['thumb_size'], None,
fit=settings['thumb_fit'], options=options)
def process_video(filepath, outpath, settings):
"""Process one image: resize, create thumbnail."""
filename = os.path.split(filepath)[1]
base, ext = os.path.splitext(filename)
outname = join(outpath, base + '.webm')
if settings['keep_orig']:
shutil.copy(filepath, join(outpath, settings['orig_dir'], filename))
sigal.video.generate_video(filepath, outname)
if settings['make_thumbs']:
thumb_name = join(outpath, get_thumb(settings, base + '.jpg'))
sigal.video.generate_thumbnail(outname, thumb_name)
def get_metadata(path):
""" Get album metadata from DESCRIPTION_FILE:

View File

@@ -38,7 +38,8 @@ _DEFAULT_CONFIG = {
'orig_dir': 'original',
'jpg_options': {'quality': 85, 'optimize': True, 'progressive': True},
'copyright': '',
'ext_list': ['.jpg', '.jpeg', '.JPG', '.JPEG', '.png'],
'img_ext_list': ['.jpg', '.jpeg', '.JPG', '.JPEG', '.png'],
'vid_ext_list': ['.MOV', '.mov', '.avi', '.mp4', '.webm', '.ogv'],
'theme': 'colorbox',
'write_html': True,
'index_in_url': False,

View File

@@ -13,7 +13,7 @@
<link rel="stylesheet" href="{{ theme.url }}/css/style.min.css">
<link rel="stylesheet" href="{{ theme.url }}/css/galleria.classic.css">
<!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
@@ -57,7 +57,7 @@
</div>
{% endif %}
{% if images %}
{% if image or videos %}
<div id="gallery">
{% for image in images %}
<a href="{{ image.file }}">
@@ -66,6 +66,10 @@
{%- if image.big %} data-description="<a href='{{ image.big }}'>Full size</a>"{% endif %}/>
</a>
{% endfor %}
{% for video in videos %}
<img src="{{ video.thumb }}" alt="{{ video.file }}"
data-layer="<video style='position:inherit'; top:50%; left:50%;margin-top:-150px;margin-left:-200px' controls> <source src={{ video.file }} type='video/webm' /></video>" />
{% endfor %}
</div>
{% endif %}
@@ -82,7 +86,7 @@
</footer>
</div>
{% if images %}
{% if images or videos %}
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script>!window.jQuery && document.write(unescape('%3Cscript src="{{ theme.url }}/js/jquery-1.8.2.min.js"%3E%3C/script%3E'))</script>
<script src="{{ theme.url }}/js/galleria-1.2.9.min.js"></script>

44
sigal/video.py Normal file
View File

@@ -0,0 +1,44 @@
# -*- coding:utf-8 -*-
# Copyright (c) 2013 - Christophe-Marie Duquesne
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
from __future__ import with_statement
import subprocess
def generate_video(source, outname, options=['-vf', 'scale=800:trunc(ow/a/2)*2']):
# http://stackoverflow.com/questions/8218363/maintaining-ffmpeg-aspect-ratio
"""Video processor
:param source: path to an image
:param outname: name of the generated video
:param options: array of options passed to ffmpeg
"""
with open("/dev/null") as devnull:
subprocess.call(['ffmpeg', '-i', source, '-y'] + options +
[outname], stderr=devnull)
def generate_thumbnail(source, outname, options=['-vf', 'scale=80:trunc(ow/a/2)*2']):
# http://stackoverflow.com/questions/8218363/maintaining-ffmpeg-aspect-ratio
"Create a thumbnail image"
with open("/dev/null") as devnull:
subprocess.call(['ffmpeg', '-i', source, '-an', '-r', '1',
'-vframes', '1', '-y'] + options + [outname], stderr=devnull)

View File

@@ -90,6 +90,7 @@ class Writer(object):
'sigal_link': sigal_link,
'theme': {'name': os.path.basename(self.theme)},
'images': [],
'videos': [],
'albums': [],
'breadcumb': ''
}
@@ -141,6 +142,14 @@ class Writer(object):
img_ctx['big'] = get_orig(self.settings, i)
ctx['images'].append(img_ctx)
for i in paths[relpath]['vid']:
base, ext = os.path.splitext(i)
vid_ctx = {'file': base + '.webm',
'thumb': get_thumb(self.settings, base + '.jpg')}
if self.settings['keep_orig']:
vid_ctx['big'] = get_orig(self.settings, i)
ctx['videos'].append(vid_ctx)
for d in paths[relpath]['subdir']:
dpath = os.path.normpath(os.path.join(relpath, d))
alb_thumb = paths[dpath]['thumbnail']

View File

@@ -37,6 +37,12 @@ REF = {
'title': u'Accentué',
'thumbnail': u'hélicoïde.jpg',
'img': [u'hélicoïde.jpg', 'superdupont_source_wikipedia_en.jpg']
},
'video': {
'title': 'Video',
'thumbnail': '',
'img': [],
'video': []
}
}
@@ -47,7 +53,8 @@ def paths():
default_conf = os.path.join(SAMPLE_DIR, 'sigal.conf.py')
settings = read_settings(default_conf)
return PathsDb(os.path.join(SAMPLE_DIR, 'pictures'), settings['ext_list'])
return PathsDb(os.path.join(SAMPLE_DIR, 'pictures'),
settings['img_ext_list'], settings['vid_ext_list'])
@pytest.fixture(scope='module')
@@ -58,14 +65,15 @@ def db(paths):
def test_filelist(db):
assert set(db.keys()) == set(['paths_list', 'skipped_dir', '.',
'dir1', 'dir2', 'dir1/test1', 'dir1/test2', u'accentué'])
'dir1', 'dir2', 'dir1/test1', 'dir1/test2', u'accentué', 'video'])
assert set(db['paths_list']) == set(['.', 'dir1', 'dir1/test1',
'dir1/test2', 'dir2', u'accentué'])
'dir1/test2', 'dir2', u'accentué', 'video'])
assert set(db['skipped_dir']) == set(['empty', 'dir1/empty'])
assert db['.']['img'] == []
assert set(db['.']['subdir']) == set([u'accentué', 'dir1', 'dir2'])
assert set(db['.']['subdir']) == set([u'accentué', 'dir1', 'dir2',
'video'])
def test_title(db):
@@ -86,7 +94,8 @@ def test_imglist(db):
def test_get_subdir(paths):
assert set(paths.get_subdirs('dir1')) == set(['dir1/test1', 'dir1/test2'])
assert set(paths.get_subdirs('.')) == set(['dir1', 'dir2', 'dir1/test1',
'dir1/test2', u'accentué'])
'dir1/test2', u'accentué',
'video'])
def test_get_metadata():