Adding video support
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
44
sigal/video.py
Normal 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)
|
||||
@@ -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']
|
||||
|
||||
Binary file not shown.
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user