Merge pull request #345 from saimn/special-chars
Quote special characters in urls
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,6 +3,7 @@
|
||||
*.pyc
|
||||
*_flymake
|
||||
.coverage
|
||||
.DS_Store
|
||||
.pytest_cache/
|
||||
.ropeproject/
|
||||
.tox/
|
||||
@@ -13,4 +14,4 @@ dist/
|
||||
docs/_build
|
||||
htmlcov/
|
||||
output/
|
||||
.DS_Store
|
||||
tags
|
||||
|
||||
@@ -6,3 +6,6 @@ ignore =
|
||||
.coveragerc
|
||||
tests
|
||||
readthedocs.yml
|
||||
|
||||
[flake8]
|
||||
ignore = E731
|
||||
|
||||
@@ -60,6 +60,7 @@ class Media:
|
||||
- ``filename``: Filename of the resized image.
|
||||
- ``thumbnail``: Location of the corresponding thumbnail image.
|
||||
- ``big``: If not None, location of the unmodified image.
|
||||
- ``big_url``: If not None, url of the unmodified image.
|
||||
- ``exif``: If not None contains a dict with the most common tags. For more
|
||||
information, see :ref:`simple-exif-data`.
|
||||
- ``raw_exif``: If not ``None``, it contains the raw EXIF tags.
|
||||
@@ -69,7 +70,7 @@ class Media:
|
||||
type = ''
|
||||
|
||||
def __init__(self, filename, path, settings):
|
||||
self.src_filename = self.filename = self.url = filename
|
||||
self.src_filename = self.filename = filename
|
||||
self.path = path
|
||||
self.settings = settings
|
||||
self.ext = os.path.splitext(filename)[1].lower()
|
||||
@@ -82,6 +83,9 @@ class Media:
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self._get_metadata()
|
||||
# default: title is the filename
|
||||
if not self.title:
|
||||
self.title = self.filename
|
||||
signals.media_initialized.send(self)
|
||||
|
||||
def __repr__(self):
|
||||
@@ -90,6 +94,11 @@ class Media:
|
||||
def __str__(self):
|
||||
return join(self.path, self.filename)
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""URL of the media."""
|
||||
return url_from_path(self.filename)
|
||||
|
||||
@property
|
||||
def big(self):
|
||||
"""Path to the original image, if ``keep_orig`` is set (relative to the
|
||||
@@ -104,9 +113,14 @@ class Media:
|
||||
check_or_create_dir(orig_path)
|
||||
big_path = join(orig_path, self.src_filename)
|
||||
if not isfile(big_path):
|
||||
copy(self.src_path, big_path,
|
||||
symlink=s['orig_link'])
|
||||
return url_from_path(join(s['orig_dir'], self.src_filename))
|
||||
copy(self.src_path, big_path, symlink=s['orig_link'])
|
||||
return join(s['orig_dir'], self.src_filename)
|
||||
|
||||
@property
|
||||
def big_url(self):
|
||||
"""URL of the original media."""
|
||||
if self.big is not None:
|
||||
return url_from_path(self.big)
|
||||
|
||||
@property
|
||||
def thumbnail(self):
|
||||
@@ -219,7 +233,7 @@ class Video(Media):
|
||||
if not settings['use_orig'] or not is_valid_html5_video(ext):
|
||||
video_format = settings['video_format']
|
||||
ext = '.' + video_format
|
||||
self.filename = self.url = base + ext
|
||||
self.filename = base + ext
|
||||
self.mime = get_mime(ext)
|
||||
self.dst_path = join(settings['destination'], path, base + ext)
|
||||
else:
|
||||
@@ -409,16 +423,16 @@ class Album:
|
||||
|
||||
if self._thumbnail:
|
||||
# stop if it is already set
|
||||
return url_from_path(self._thumbnail)
|
||||
return self._thumbnail
|
||||
|
||||
# Test the thumbnail from the Markdown file.
|
||||
thumbnail = self.meta.get('thumbnail', [''])[0]
|
||||
|
||||
if thumbnail and isfile(join(self.src_path, thumbnail)):
|
||||
self._thumbnail = join(self.name, get_thumb(self.settings,
|
||||
thumbnail))
|
||||
self._thumbnail = url_from_path(join(
|
||||
self.name, get_thumb(self.settings, thumbnail)))
|
||||
self.logger.debug("Thumbnail for %r : %s", self, self._thumbnail)
|
||||
return url_from_path(self._thumbnail)
|
||||
return self._thumbnail
|
||||
else:
|
||||
# find and return the first landscape image
|
||||
for f in self.medias:
|
||||
@@ -431,17 +445,19 @@ class Album:
|
||||
size = get_size(f.src_path)
|
||||
|
||||
if size['width'] > size['height']:
|
||||
self._thumbnail = join(self.name, f.thumbnail)
|
||||
self._thumbnail = (url_quote(self.name) + '/' +
|
||||
f.thumbnail)
|
||||
self.logger.debug(
|
||||
"Use 1st landscape image as thumbnail for %r :"
|
||||
" %s", self, self._thumbnail)
|
||||
return url_from_path(self._thumbnail)
|
||||
"Use 1st landscape image as thumbnail for %r : %s",
|
||||
self, self._thumbnail)
|
||||
return self._thumbnail
|
||||
|
||||
# else simply return the 1st media file
|
||||
if not self._thumbnail and self.medias:
|
||||
for media in self.medias:
|
||||
if media.thumbnail is not None:
|
||||
self._thumbnail = join(self.name, media.thumbnail)
|
||||
self._thumbnail = (url_quote(self.name) + '/' +
|
||||
media.thumbnail)
|
||||
break
|
||||
else:
|
||||
self.logger.warning("No thumbnail found for %r", self)
|
||||
@@ -449,17 +465,18 @@ class Album:
|
||||
|
||||
self.logger.debug("Use the 1st image as thumbnail for %r : %s",
|
||||
self, self._thumbnail)
|
||||
return url_from_path(self._thumbnail)
|
||||
return self._thumbnail
|
||||
|
||||
# use the thumbnail of their sub-directories
|
||||
if not self._thumbnail:
|
||||
for path, album in self.gallery.get_albums(self.path):
|
||||
if album.thumbnail:
|
||||
self._thumbnail = join(self.name, album.thumbnail)
|
||||
self._thumbnail = (url_quote(self.name) + '/' +
|
||||
album.thumbnail)
|
||||
self.logger.debug(
|
||||
"Using thumbnail from sub-directory for %r : %s",
|
||||
self, self._thumbnail)
|
||||
return url_from_path(self._thumbnail)
|
||||
return self._thumbnail
|
||||
|
||||
self.logger.error('Thumbnail not found for %r', self)
|
||||
return None
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
{% if album.medias %}
|
||||
{% macro img_description(media) -%}
|
||||
{% if media.big %} data-big="{{ media.big }}"{% endif %}
|
||||
{% if media.big %} data-big="{{ media.big_url }}"{% endif %}
|
||||
{% if media.exif %}
|
||||
{% if media.exif.datetime %}
|
||||
data-date=", {{ media.exif.datetime }}"
|
||||
@@ -48,39 +48,37 @@
|
||||
{% if loop.index % nb_columns == 1 %}
|
||||
<div id="albums" class="row">
|
||||
{% endif%}
|
||||
{% set media_title = media.title if media.title else media.filename %}
|
||||
{% if media.type == "image" %}
|
||||
<div class="{{ column_size_t }} columns thumbnail">
|
||||
{% if 'sigal.plugins.media_page' in settings.plugins %}
|
||||
<a href="{{ media.filename}}.html" class="gallery"
|
||||
title="{{ media_title }}"
|
||||
data-href="{{ media.filename }}" {{ img_description(media) }}>
|
||||
<a href="{{ media.url}}.html" class="gallery"
|
||||
title="{{ media.title }}"
|
||||
data-href="{{ media.url }}" {{ img_description(media) }}>
|
||||
{% else %}
|
||||
<a href="{{ media.filename }}" class="gallery"
|
||||
title="{{ media_title }}"
|
||||
<a href="{{ media.url }}" class="gallery" title="{{ media.title }}"
|
||||
{{ img_description(media) }}>
|
||||
{% endif %}
|
||||
<img src="{{ media.thumbnail }}" alt="{{ media.filename }}"
|
||||
title="{{ media_title }}" /></a>
|
||||
<img src="{{ media.thumbnail }}" alt="{{ media.url }}"
|
||||
title="{{ media.title }}" /></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if media.type == "video" %}
|
||||
{% set mhash = media.filename|replace('.', '')|replace(' ', '') %}
|
||||
{% set mhash = media.url|replace('.', '')|replace(' ', '') %}
|
||||
<div class="{{ column_size_t }} columns thumbnail">
|
||||
{% if 'sigal.plugins.media_page' in settings.plugins %}
|
||||
<a href="{{ media.filename }}.html" data-href="#{{ mhash }}"
|
||||
<a href="{{ media.url }}.html" data-href="#{{ mhash }}"
|
||||
{% else %}
|
||||
<a href="#{{ mhash }}" class="gallery" inline='yes' title="{{ media.filename }}"
|
||||
<a href="#{{ mhash }}" class="gallery" inline='yes' title="{{ media.url }}"
|
||||
{% endif %}
|
||||
{% if media.big %} data-big="{{ media.big }}"{% endif %}>
|
||||
<img src="{{ media.thumbnail }}" alt="{{ media.filename }}"
|
||||
title="{{ media_title }}" /></a>
|
||||
{% if media.big %} data-big="{{ media.big_url }}"{% endif %}>
|
||||
<img src="{{ media.thumbnail }}" alt="{{ media.url }}"
|
||||
title="{{ media.title }}" /></a>
|
||||
</div>
|
||||
<!-- This contains the hidden content for the video -->
|
||||
<div style='display:none'>
|
||||
<div id="{{ mhash }}">
|
||||
<video controls>
|
||||
<source src='{{ media.filename }}' type='{{ media.mime }}' />
|
||||
<source src='{{ media.url }}' type='{{ media.mime }}' />
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% set media_title = media.title if media.title else media.filename %}
|
||||
<header>
|
||||
{% if album.breadcrumb %}
|
||||
<h2>
|
||||
{% for url, title in album.breadcrumb %}
|
||||
<a href="{{ url }}">{{ title }}</a> »
|
||||
{% endfor -%}
|
||||
<a href="#">{{ media_title }}</a>
|
||||
<a href="#">{{ media.title }}</a>
|
||||
</h2>
|
||||
<hr>
|
||||
{% endif %}
|
||||
@@ -16,20 +15,20 @@
|
||||
{% if media %}
|
||||
<div class="thumbnail">
|
||||
{% if media.type == "image" %}
|
||||
<img src="{{ media.filename }}" alt="{{ media_title }}" title="{{ media_title }}" />
|
||||
<img src="{{ media.url }}" alt="{{ media.title }}" title="{{ media.title }}" />
|
||||
{% endif %}
|
||||
{% if media.type == "video" %}
|
||||
<video controls>
|
||||
<source src='{{ media.filename }}' type='video/webm' />
|
||||
<source src='{{ media.url }}' type='video/webm' />
|
||||
</video>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
{% if previous_media %}
|
||||
<a href="{{ previous_media.filename }}.html">« previous</a>
|
||||
<a href="{{ previous_media.url }}.html">« previous</a>
|
||||
{% endif %}
|
||||
{% if next_media %}
|
||||
<a href="{{ next_media.filename }}.html">next »</a>
|
||||
<a href="{{ next_media.url }}.html">next »</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<style>
|
||||
body::after{
|
||||
position:absolute; width:0; height:0; overflow:hidden; z-index:-1;
|
||||
content:url({{ album.medias[0].filename }});
|
||||
content:url({{ album.medias[0].url }});
|
||||
}</style>
|
||||
{% endif %}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
{% block late_js %}
|
||||
{% macro img_description(media) -%}
|
||||
{%- if media.big -%}<a href='{{ media.big }}'>Full size</a>{%- endif -%}
|
||||
{%- if media.big -%}<a href='{{ media.big_url }}'>Full size</a>{%- endif -%}
|
||||
{# clean up tags and whitespace, including newlines, in the description #}
|
||||
{%- if media.description -%}<br>{{ media.description | striptags }}{%- endif -%}
|
||||
{%- if media.exif -%}
|
||||
@@ -81,18 +81,18 @@
|
||||
var data = [
|
||||
{% for media in album.medias -%}
|
||||
{
|
||||
title: "{{ media.title if media.title else media.filename }}",
|
||||
title: "{{ media.title }}",
|
||||
description: "{{ img_description(media) | e }}",
|
||||
thumb: "{{ media.thumbnail }}",
|
||||
{% if media.big %}
|
||||
big: "{{ media.big }}",
|
||||
big: "{{ media.big_url }}",
|
||||
{% endif %}
|
||||
{% if media.type == "image" %}
|
||||
image: "{{ media.filename }}"
|
||||
image: "{{ media.url }}"
|
||||
{% endif %}
|
||||
{% if media.type == "video" %}
|
||||
image: "{{ theme.url }}/img/empty.png",
|
||||
layer: "<video controls><source src='{{ media.filename }}' type='{{ media.mime }}' /></video>"
|
||||
layer: "<video controls><source src='{{ media.url }}' type='{{ media.mime }}' /></video>"
|
||||
{% endif %}
|
||||
},
|
||||
{% endfor %}
|
||||
|
||||
@@ -67,27 +67,26 @@
|
||||
{% if album.medias %}
|
||||
<div class="gallery" itemscope itemtype="http://schema.org/ImageGallery">
|
||||
{% for media in album.medias %}
|
||||
{% set media_title = media.title if media.title else media.filename %}
|
||||
{% if media.type == "image" %}
|
||||
{% if loop.first %}
|
||||
<figure class="gallery__img--main thumbnail" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject">
|
||||
<a href="{{ media.filename }}" itemprop="contentUrl" data-size="{{media.size.width}}x{{media.size.height}}">
|
||||
<img src="{{ theme.url }}/echo/blank.gif" data-echo="{{ media.filename }}" alt="{{ media.filename }}" itemprop="thumbnail" title="{{ media.exif.datetime }}" />
|
||||
<a href="{{ media.url }}" itemprop="contentUrl" data-size="{{media.size.width}}x{{media.size.height}}">
|
||||
<img src="{{ theme.url }}/echo/blank.gif" data-echo="{{ media.url }}" alt="{{ media.url }}" itemprop="thumbnail" title="{{ media.exif.datetime }}" />
|
||||
</a>
|
||||
<figcaption itemprop="caption description">{{ media_title }} - {{ media.exif.datetime }}</figcaption>
|
||||
<figcaption itemprop="caption description">{{ media.title }} - {{ media.exif.datetime }}</figcaption>
|
||||
</figure>
|
||||
{% else %}
|
||||
<figure class="gallery__img--secondary thumbnail" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject">
|
||||
<a href="{{ media.filename }}" itemprop="contentUrl" data-size="{{media.size.width}}x{{media.size.height}}">
|
||||
<img src="{{ theme.url }}/echo/blank.gif" data-echo="{{ media.thumbnail }}" alt="{{ media.filename }}" itemprop="thumbnail" title="{{ media.exif.datetime }}" />
|
||||
<a href="{{ media.url }}" itemprop="contentUrl" data-size="{{media.size.width}}x{{media.size.height}}">
|
||||
<img src="{{ theme.url }}/echo/blank.gif" data-echo="{{ media.thumbnail }}" alt="{{ media.url }}" itemprop="thumbnail" title="{{ media.exif.datetime }}" />
|
||||
</a>
|
||||
<figcaption itemprop="caption description">{{ media_title }} - {{ media.exif.datetime }}</figcaption>
|
||||
<figcaption itemprop="caption description">{{ media.title }} - {{ media.exif.datetime }}</figcaption>
|
||||
</figure>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if media.type == "video" %}
|
||||
<figure class="gallery__img--secondary thumbnail" itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject">
|
||||
<a href="{{ media.filename }}" itemprop="contentUrl" data-type="video" data-video='<div class="video"><div class="video__container"><video controls><source src="{{ media.filename }}" type="{{ media.mime }}" /></video></div></div>'>
|
||||
<a href="{{ media.url }}" itemprop="contentUrl" data-type="video" data-video='<div class="video"><div class="video__container"><video controls><source src="{{ media.url }}" type="{{ media.mime }}" /></video></div></div>'>
|
||||
<img src="{{ theme.url }}/echo/blank.gif" data-echo="{{ media.thumbnail }}" alt="{{ media.thumbnail }}" itemprop="thumbnail" title="" />
|
||||
</a>
|
||||
<figcaption itemprop="caption description">{{ media_title }}</figcaption>
|
||||
|
||||
@@ -23,6 +23,8 @@ import shutil
|
||||
from markdown import Markdown
|
||||
from markupsafe import Markup
|
||||
|
||||
from urllib.parse import quote
|
||||
|
||||
VIDEO_MIMES = {'.mp4': 'video/mp4',
|
||||
'.webm': 'video/webm',
|
||||
'.ogv': 'video/ogg'}
|
||||
@@ -58,10 +60,9 @@ def check_or_create_dir(path):
|
||||
def url_from_path(path):
|
||||
"""Transform path to url, converting backslashes to slashes if needed."""
|
||||
|
||||
if os.sep == '/':
|
||||
return path
|
||||
else:
|
||||
return '/'.join(path.split(os.sep))
|
||||
if os.sep != '/':
|
||||
path = '/'.join(path.split(os.sep))
|
||||
return quote(path)
|
||||
|
||||
|
||||
def read_markdown(filename):
|
||||
|
||||
BIN
tests/sample/pictures/accentué/test?<special> chars#.jpg
Normal file
BIN
tests/sample/pictures/accentué/test?<special> chars#.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 212 KiB |
@@ -4,6 +4,7 @@ import pytest
|
||||
import datetime
|
||||
|
||||
from os.path import join
|
||||
from urllib.parse import quote
|
||||
from sigal.gallery import Album, Media, Image, Video, Gallery
|
||||
from sigal.video import SubprocessException
|
||||
|
||||
@@ -53,7 +54,7 @@ REF = {
|
||||
'accentué': {
|
||||
'title': 'accentué',
|
||||
'name': 'accentué',
|
||||
'thumbnail': 'accentué/thumbnails/hélicoïde.tn.jpg',
|
||||
'thumbnail': 'accentu%C3%A9/thumbnails/h%C3%A9lico%C3%AFde.tn.jpg',
|
||||
'subdirs': [],
|
||||
'medias': ['hélicoïde.jpg', '11.jpg'],
|
||||
},
|
||||
@@ -61,7 +62,7 @@ REF = {
|
||||
'title': 'video',
|
||||
'name': 'video',
|
||||
'thumbnail': ('video/thumbnails/'
|
||||
'example video.tn.jpg'),
|
||||
'example%20video.tn.jpg'),
|
||||
'subdirs': [],
|
||||
'medias': ['example video.ogv']
|
||||
}
|
||||
@@ -99,7 +100,7 @@ def test_media_orig(settings, tmpdir):
|
||||
|
||||
m = Video('example video.ogv', 'video', settings)
|
||||
assert m.filename == 'example video.webm'
|
||||
assert m.big == 'original/example video.ogv'
|
||||
assert m.big_url == 'original/example%20video.ogv'
|
||||
assert os.path.isfile(join(settings['destination'], m.path, m.big))
|
||||
|
||||
settings['use_orig'] = True
|
||||
@@ -144,7 +145,7 @@ def test_video(settings, tmpdir):
|
||||
assert m.dst_path == join(settings['destination'], file_path)
|
||||
|
||||
os.makedirs(join(settings['destination'], 'video', 'thumbnails'))
|
||||
assert m.thumbnail == join('thumbnails', 'example video.tn.jpg')
|
||||
assert m.thumbnail == join('thumbnails', 'example%20video.tn.jpg')
|
||||
assert os.path.isfile(m.thumb_path)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user