sorl-thumbnail/ 0000755 0001750 0001750 00000000000 13141176121 012147 5 ustar wmb wmb sorl-thumbnail/MANIFEST.in 0000644 0001750 0001750 00000000172 13141176077 013717 0 ustar wmb wmb include LICENSE README.rst
recursive-include docs *
recursive-include tests *
recursive-exclude * *.pyc
prune docs/_build
sorl-thumbnail/.editorconfig 0000644 0001750 0001750 00000000660 13141176077 014640 0 ustar wmb wmb ; This file is for unifying the coding style for different editors and IDEs.
; More information at http://EditorConfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
[*.py]
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[*.rst]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
[.travis.yml]
indent_style = space
indent_size = 2
sorl-thumbnail/.gitignore 0000644 0001750 0001750 00000001223 13141176077 014147 0 ustar wmb wmb # Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# Distribution / packaging
env/
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
*.egg
.installed.cfg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Translations
*.mo
# Related to Desktops and OS
.DS_Store
.AppleDouble
.LSOverride
._*
.~
.bak
*.swp
*.swo
*.swn
# VCS and DVCS
.svn
# Common IDE's
.idea
.project
.pydevproject
.ropeproject
# Sphinx documentation
docs/_build/
# Virtualenv
.python-version
.env/
# dbm stuff
tests/thumbnail_kvstore
tests/thumbnail_kvstore.db
tests/thumbnail_kvstore.lock
# test related
.coverage
.tox
htmlcov/
sorl-thumbnail/README.rst 0000644 0001750 0001750 00000013455 13141176077 013660 0 ustar wmb wmb |travis| |pypi| |coveralls|
Thumbnails for Django.
Features at a glance
====================
- Support for Django 1.8, 1.10, 1.11, following the `Django supported versions policy`_
- Python 3 support
- Storage support
- Pluggable Engine support for `Pillow`_, `ImageMagick`_, `PIL`_, `Wand`_, `pgmagick`_, and `vipsthumbnail`_
- Pluggable Key Value Store support (cached db, redis, and dynamodb by AWS)
- Pluggable Backend support
- Admin integration with possibility to delete
- Dummy generation (placeholders)
- Flexible, simple syntax, generates no html
- ImageField for model that deletes thumbnails
- CSS style cropping options
- Back smart cropping, and remove borders from the images when cropping
- Margin calculation for vertical positioning
- Alternative resolutions versions of a thumbnail
Read more in `the documentation (latest version) `_
Developers
==========
|jazzband|
This is a `Jazzband `_ project. By contributing you agree to
abide by the `Contributor Code of Conduct `_
and follow the `guidelines `_.
Feel free to create a new Pull request if you want to propose a new feature.
If you need development support or want to discuss with other developers
join us in the channel #sorl-thumnbnail at freenode.net or Gitter.
For releases updates and more in deep development discussion use our mailing list
in Google Groups.
- IRC Channel: irc://irc.freenode.net/#sorl-thumbnail
- Gitter: https://gitter.im/jazzband/sorl-thumbnail
- Mailing List: sorl-thumbnail@googlegroups.com https://groups.google.com/d/forum/sorl-thumbnail
Tests
-----
The tests should run with tox and pytest. Running `tox` will run all tests for all environments.
However, it is possible to run a certain environment with `tox -e `, a list of all environments
can be found with `tox -l`. These tests require the dependencies of the different engines defined in
the documentation. It is possible to install these dependencies into a vagrant image with the
Vagrantfile in the repo.
User Support
============
If you need help using sorl-thumbnail browse http://stackoverflow.com/questions/tagged/sorl-thumbnail
and posts your questions with the `sorl-thumbnail` tag.
How to Use
==========
Get the code
------------
Getting the code for the latest stable release use 'pip'. ::
$ pip install sorl-thumbnail
Install in your project
-----------------------
Then register 'sorl.thumbnail', in the 'INSTALLED_APPS' section of
your project's settings. ::
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.admin',
'django.contrib.sites',
'django.contrib.comments',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.contenttypes',
'sorl.thumbnail',
)
Templates Usage
---------------
All of the examples assume that you first load the thumbnail template tag in
your template.::
{% load thumbnail %}
A simple usage. ::
{% thumbnail item.image "100x100" crop="center" as im %}
{% endthumbnail %}
See more examples in the section `Template examples`_ in the Documentation
Model Usage
-----------
Using the ImageField that automatically deletes references to itself in the key
value store and its thumbnail references and the thumbnail files when deleted.::
from django.db import models
from sorl.thumbnail import ImageField
class Item(models.Model):
image = ImageField(upload_to='whatever')
See more examples in the section `Model examples`_ in the Documentation
Low level API
-------------
You can use the 'get_thumbnail'::
from sorl.thumbnail import get_thumbnail
from sorl.thumbnail import delete
im = get_thumbnail(my_file, '100x100', crop='center', quality=99)
delete(my_file)
See more examples in the section `Low level API examples`_ in the Documentation
Frequently asked questions
==========================
Is so slow in Amazon S3 !
-------------------------
Possible related to the implementation of your Amazon S3 Backend, see the `issue #351`_
due the storage backend reviews if there is an existing thumbnail when tries to
generate the thumbnail that makes an extensive use of the S3 API
A fast workaround if you are not willing to tweak your storage backend is to set::
THUMBNAIL_FORCE_OVERWRITE = True
So it will avoid to overly query the S3 API.
.. |travis| image:: https://travis-ci.org/jazzband/sorl-thumbnail.svg?branch=master
:target: https://travis-ci.org/jazzband/sorl-thumbnail
.. |pypi| image:: https://badge.fury.io/py/sorl-thumbnail.png
:target: http://badge.fury.io/py/sorl-thumbnail
.. |coveralls| image:: https://coveralls.io/repos/jazzband//sorl-thumbnail/badge.png?branch=master
:target: https://coveralls.io/r/jazzband//sorl-thumbnail?branch=master
.. |jazzband| image:: https://jazzband.co/static/img/jazzband.svg
:target: https://jazzband.co/
:alt: Jazzband
.. _`Pillow`: http://pillow.readthedocs.org/en/latest/
.. _`ImageMagick`: http://www.imagemagick.org/script/index.php
.. _`PIL`: http://www.pythonware.com/products/pil/
.. _`Wand`: http://docs.wand-py.org/
.. _`pgmagick`: http://pgmagick.readthedocs.org/en/latest/
.. _`vipsthumbnail`: http://www.vips.ecs.soton.ac.uk/index.php?title=VIPS
.. _`Template examples`: http://sorl-thumbnail.readthedocs.org/en/latest/examples.html#template-examples
.. _`Model examples`: http://sorl-thumbnail.readthedocs.org/en/latest/examples.html#model-examples
.. _`Low level API examples`: http://sorl-thumbnail.readthedocs.org/en/latest/examples.html#low-level-api-examples
.. _`issue #351`: https://github.com/jazzband/sorl-thumbnail/issues/351
.. _`Django supported versions policy`: https://www.djangoproject.com/download/#supported-versions
sorl-thumbnail/vagrant.sh 0000644 0001750 0001750 00000001201 13141176077 014151 0 ustar wmb wmb #!/bin/sh
apt-get update
apt-get install -qq git python-software-properties python-pip
apt-get install -qq libjpeg62 libjpeg62-dev zlib1g-dev imagemagick graphicsmagick redis-server
apt-get install -qq libmagickwand-dev libgraphicsmagick++-dev libboost-python-dev libboost-thread-dev
apt-get install -qq libvips-tools
add-apt-repository -y ppa:fkrull/deadsnakes
apt-get update
apt-get install -qq python2.7 python2.7-dev python3.4 python3.4-dev
pip install tox
# Fix locale to allow saving unicoded filenames
echo 'LANG=en_US.UTF-8' > /etc/default/locale
# Start in project dir by default
echo "\n\ncd /vagrant" >> /home/vagrant/.bashrc
sorl-thumbnail/setup.cfg 0000644 0001750 0001750 00000005030 13141176077 014000 0 ustar wmb wmb [wheel]
universal = 1
[bdist_wheel]
universal = 1
;; flake8 analyzes and detect varios errors in the source code.
[flake8]
# D105 - Missing docstring in magic method `__func__`
ignore = D105
max-line-length = 100
exclude = .git,
.tox,
docs/,
*/migrations/*,
tests/settings/
tests/thumbnail_tests/compat.py,
sorl/thumbnail/__init__.py,
sorl/thumbnail/admin/__init__.py,
sorl/thumbnail/compat.py
max_complexity = 15
;; Coverage.py, Code coverage testing for Python.
[cov:run]
source = sorl
omit = */sorl-thumbnail/sorl/__init__.py
*/sorl/thumbnail/__init__.py
*/sorl/thumbnail/conf/__init__.py
*/sorl/thumbnail/admin/compat.py
*/sorl/thumbnail/admin/__init__.py
*/sorl/thumbnail/compat.py
[cov:report]
exclude_lines = pragma: no cover
if __name__ == .__main__.:
;; The pytest framework
[tool:pytest]
python_files = test_*.py *tests.py
norecursedirs = .* tmp* __pycache__
testpaths = tests
django_find_project = false
;; Tox is a generic virtualenv management to run tests in different configurations.
[tox]
skipsdist = True
envlist = {py27,py33,py34,py35}-django18-{pil,imagemagick,graphicsmagick,redis,dynamodb,wand,pgmagick,dbm,vipsthumbnail},
{py27,py34,py35}-django110-{pil,imagemagick,graphicsmagick,redis,dynamodb,wand,pgmagick,dbm,vipsthumbnail},
{py27,py34,py35,py36}-django111-{pil,imagemagick,graphicsmagick,redis,dynamodb,wand,pgmagick,dbm,vipsthumbnail}
[testenv]
changedir = {toxinidir}/tests
deps = pytest
pytest-cov
coverage <= 3.7.1
pytest-django
Pillow
redis: redis
dynamodb: boto
pgmagick: pgmagick
wand: wand
django18: Django>=1.8,<1.9
django19: Django>=1.9,<1.10
django110: Django>=1.10a1,<1.11
django111: Django>=1.11a,<1.12
setenv = PYTHONPATH = {toxinidir}:{toxinidir}
pil: DJANGO_SETTINGS_MODULE=tests.settings.pil
imagemagick: DJANGO_SETTINGS_MODULE=tests.settings.imagemagick
graphicsmagick: DJANGO_SETTINGS_MODULE=tests.settings.graphicsmagick
vipsthumbnail: DJANGO_SETTINGS_MODULE=tests.settings.vipsthumbnail
redis: DJANGO_SETTINGS_MODULE=tests.settings.redis
dynamodb: DJANGO_SETTINGS_MODULE=tests.settings.dynamodb
wand: DJANGO_SETTINGS_MODULE=tests.settings.wand
pgmagick: DJANGO_SETTINGS_MODULE=tests.settings.pgmagick
dbm: DJANGO_SETTINGS_MODULE=tests.settings.dbm
commands = py.test -rw --cov-config setup.cfg --cov sorl
sorl-thumbnail/setup.py 0000644 0001750 0001750 00000003052 13141176077 013673 0 ustar wmb wmb # -*- encoding: utf8 -*-
from setuptools import setup, find_packages
from setuptools.command.test import test
from sorl import __version__, __author__, __maintainer__, __email__, __license__
class TestCommand(test):
def run(self):
from tests.runtests import runtests
runtests()
setup(
name='sorl-thumbnail',
version=__version__,
description='Thumbnails for Django',
long_description=open('README.rst').read(),
author=__author__,
author_email='mikko@aino.se',
maintainer=__maintainer__,
maintainer_email=__email__,
license=__license__,
url='https://github.com/jazzband/sorl-thumbnail',
packages=find_packages(exclude=['tests', 'tests.*']),
platforms='any',
zip_safe=False,
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Multimedia :: Graphics',
'Framework :: Django',
'Framework :: Django :: 1.8',
'Framework :: Django :: 1.10',
'Framework :: Django :: 1.11',
],
cmdclass={"test": TestCommand},
)
sorl-thumbnail/Vagrantfile 0000644 0001750 0001750 00000000427 13141176077 014351 0 ustar wmb wmb # -*- mode: ruby -*-
# vi: set ft=ruby :
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "precise64"
config.vm.box_url = "http://files.vagrantup.com/precise64.box"
config.vm.provision :shell, :path => "vagrant.sh"
end
sorl-thumbnail/docs/ 0000755 0001750 0001750 00000000000 13141176077 013111 5 ustar wmb wmb sorl-thumbnail/docs/make.bat 0000644 0001750 0001750 00000010030 13141176077 014510 0 ustar wmb wmb @ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^` where ^ is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\sorlthumbnail.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\sorlthumbnail.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end
sorl-thumbnail/docs/_theme/ 0000755 0001750 0001750 00000000000 13141176077 014352 5 ustar wmb wmb sorl-thumbnail/docs/_theme/nature/ 0000755 0001750 0001750 00000000000 13141176077 015650 5 ustar wmb wmb sorl-thumbnail/docs/_theme/nature/static/ 0000755 0001750 0001750 00000000000 13141176077 017137 5 ustar wmb wmb sorl-thumbnail/docs/_theme/nature/static/pygments.css 0000644 0001750 0001750 00000005235 13141176077 021524 0 ustar wmb wmb .c { color: #999988; font-style: italic } /* Comment */
.k { font-weight: bold } /* Keyword */
.o { font-weight: bold } /* Operator */
.cm { color: #999988; font-style: italic } /* Comment.Multiline */
.cp { color: #999999; font-weight: bold } /* Comment.preproc */
.c1 { color: #999988; font-style: italic } /* Comment.Single */
.gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.ge { font-style: italic } /* Generic.Emph */
.gr { color: #aa0000 } /* Generic.Error */
.gh { color: #999999 } /* Generic.Heading */
.gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.go { color: #111 } /* Generic.Output */
.gp { color: #555555 } /* Generic.Prompt */
.gs { font-weight: bold } /* Generic.Strong */
.gu { color: #aaaaaa } /* Generic.Subheading */
.gt { color: #aa0000 } /* Generic.Traceback */
.kc { font-weight: bold } /* Keyword.Constant */
.kd { font-weight: bold } /* Keyword.Declaration */
.kp { font-weight: bold } /* Keyword.Pseudo */
.kr { font-weight: bold } /* Keyword.Reserved */
.kt { color: #445588; font-weight: bold } /* Keyword.Type */
.m { color: #009999 } /* Literal.Number */
.s { color: #bb8844 } /* Literal.String */
.na { color: #008080 } /* Name.Attribute */
.nb { color: #999999 } /* Name.Builtin */
.nc { color: #445588; font-weight: bold } /* Name.Class */
.no { color: #ff99ff } /* Name.Constant */
.ni { color: #800080 } /* Name.Entity */
.ne { color: #990000; font-weight: bold } /* Name.Exception */
.nf { color: #990000; font-weight: bold } /* Name.Function */
.nn { color: #555555 } /* Name.Namespace */
.nt { color: #000080 } /* Name.Tag */
.nv { color: purple } /* Name.Variable */
.ow { font-weight: bold } /* Operator.Word */
.mf { color: #009999 } /* Literal.Number.Float */
.mh { color: #009999 } /* Literal.Number.Hex */
.mi { color: #009999 } /* Literal.Number.Integer */
.mo { color: #009999 } /* Literal.Number.Oct */
.sb { color: #bb8844 } /* Literal.String.Backtick */
.sc { color: #bb8844 } /* Literal.String.Char */
.sd { color: #bb8844 } /* Literal.String.Doc */
.s2 { color: #bb8844 } /* Literal.String.Double */
.se { color: #bb8844 } /* Literal.String.Escape */
.sh { color: #bb8844 } /* Literal.String.Heredoc */
.si { color: #bb8844 } /* Literal.String.Interpol */
.sx { color: #bb8844 } /* Literal.String.Other */
.sr { color: #808000 } /* Literal.String.Regex */
.s1 { color: #bb8844 } /* Literal.String.Single */
.ss { color: #bb8844 } /* Literal.String.Symbol */
.bp { color: #999999 } /* Name.Builtin.Pseudo */
.vc { color: #ff99ff } /* Name.Variable.Class */
.vg { color: #ff99ff } /* Name.Variable.Global */
.vi { color: #ff99ff } /* Name.Variable.Instance */
.il { color: #009999 } /* Literal.Number.Integer.Long */ sorl-thumbnail/docs/_theme/nature/static/nature.css_t 0000644 0001750 0001750 00000007440 13141176077 021477 0 ustar wmb wmb /**
* Sphinx stylesheet -- default theme
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: Arial, sans-serif;
font-size: 100%;
background-color: #111;
color: #555;
margin: 0;
padding: 0;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 230px;
}
hr{
border: 1px solid #B1B4B6;
}
div.document {
background-color: #eee;
}
div.body {
background-color: #ffffff;
color: #3E4349;
padding: 0 30px 30px 30px;
font-size: 0.8em;
}
div.footer {
color: #555;
width: 100%;
padding: 13px 0;
text-align: center;
font-size: 75%;
}
div.footer a {
color: #444;
text-decoration: underline;
}
div.related {
background-color: #6BA81E;
line-height: 32px;
color: #fff;
text-shadow: 0px 1px 0 #444;
font-size: 0.80em;
}
div.related a {
color: #E2F3CC;
}
div.sphinxsidebar {
font-size: 0.75em;
line-height: 1.5em;
}
div.sphinxsidebarwrapper{
padding: 20px 0;
}
div.sphinxsidebar h3,
div.sphinxsidebar h4 {
font-family: Arial, sans-serif;
color: #222;
font-size: 1.2em;
font-weight: normal;
margin: 0;
padding: 5px 10px;
background-color: #ddd;
text-shadow: 1px 1px 0 white
}
div.sphinxsidebar h4{
font-size: 1.1em;
}
div.sphinxsidebar h3 a {
color: #444;
}
div.sphinxsidebar p {
color: #888;
padding: 5px 20px;
}
div.sphinxsidebar p.topless {
}
div.sphinxsidebar ul {
margin: 10px 20px;
padding: 0;
color: #000;
}
div.sphinxsidebar a {
color: #444;
}
div.sphinxsidebar input {
border: 1px solid #ccc;
font-family: sans-serif;
font-size: 1em;
}
div.sphinxsidebar input[type=text]{
margin-left: 20px;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #005B81;
text-decoration: none;
}
a:hover {
color: #E32E00;
text-decoration: underline;
}
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: Arial, sans-serif;
background-color: #BED4EB;
font-weight: normal;
color: #212224;
margin: 30px 0px 10px 0px;
padding: 5px 0 5px 10px;
text-shadow: 0px 1px 0 white
}
div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
div.body h2 { font-size: 150%; background-color: #C8D5E3; }
div.body h3 { font-size: 120%; background-color: #D8DEE3; }
div.body h4 { font-size: 110%; background-color: #D8DEE3; }
div.body h5 { font-size: 100%; background-color: #D8DEE3; }
div.body h6 { font-size: 100%; background-color: #D8DEE3; }
a.headerlink {
color: #c60f0f;
font-size: 0.8em;
padding: 0 4px 0 4px;
text-decoration: none;
}
a.headerlink:hover {
background-color: #c60f0f;
color: white;
}
div.body p, div.body dd, div.body li {
line-height: 1.5em;
}
div.admonition p.admonition-title + p {
display: inline;
}
div.highlight{
background-color: white;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre {
padding: 10px;
background-color: White;
color: #222;
line-height: 1.2em;
border: 1px solid #C6C9CB;
font-size: 1.2em;
margin: 1.5em 0 1.5em 0;
-webkit-box-shadow: 1px 1px 1px #d8d8d8;
-moz-box-shadow: 1px 1px 1px #d8d8d8;
}
tt {
background-color: #ecf0f3;
color: #222;
padding: 1px 2px;
font-size: 1.2em;
font-family: monospace;
}
sorl-thumbnail/docs/_theme/nature/theme.conf 0000644 0001750 0001750 00000000107 13141176077 017617 0 ustar wmb wmb [theme]
inherit = basic
stylesheet = nature.css
pygments_style = tango
sorl-thumbnail/docs/index.rst 0000644 0001750 0001750 00000000555 13141176077 014757 0 ustar wmb wmb ******************************
sorl-thumbnail's documentation
******************************
Contents:
.. toctree::
:maxdepth: 2
examples
installation
requirements
template
management
logging
operation
reference/index
contributing
news
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
sorl-thumbnail/docs/operation.rst 0000644 0001750 0001750 00000006173 13141176077 015652 0 ustar wmb wmb ***************************
How sorl-thumbnail operates
***************************
.. highlight:: python
When you use the ``thumbnail`` template tag sorl-thumbnail looks up the
thumbnail in a :ref:`kvstore-requirements`. The key for a thumbnail is
generated from its filename and storage. The thumbnail filename in turn is
generated from the source and requested thumbnail size and options. If the key
for the thumbnail is found in the |kvstore|, the serialized thumbnail
information is fetched from it and returned. If the thumbnail key is not found
there sorl-thumbnail continues to generate the thumbnail and stores necessary
information in the |kvstore|. It is worth noting that sorl-thumbnail does not
check if source or thumbnail exists if the thumbnail key is found in the
|kvstore|.
.. note:: This means that if you change or delete a source file or delete the
thumbnail, sorl-thumbnail will still fetch from the |kvstore|.
Therefore it is important that if you delete or change a source or
thumbnail file notify the |kvstore|.
If you change or delete a source or a thumbnail for some reason, you can use
the ``delete`` method of the ``ThumbnailBackend`` class or subclass::
from sorl.thumbnail import delete
# Delete the Key Value Store reference but **not** the file.
# Use this if you have changed the source
delete(my_file, delete_file=False)
# Delete the Key Value Store reference and the file
# Use this if you want to delete the source file
delete(my_file) # delete_file=True is default
The ``sorl.thumbnail.delete`` method always deletes the input files thumbnail
Key Value Store references as well as thumbnail files. You can use this method
on thumbnails as well as source files. Alternatively if you have **deleted** a
file you can use the management command :ref:`thumbnail-cleanup`. Deleting an
image using the ``sorl.thumbnail.ImageField`` will notify the |kvstore| to
delete references to it and delete all of its thumbnail references and files,
exactly like the above code example.
**Why you ask?** Why go through all the trouble with a |kvstore| and risk
stale cache? Why not use a database to cache if you are going to do that?
The reason is speed and especially with storages other than local file storage.
Checking if a file exists before serving it will cost too much. Speed is also
the reason for not choosing to use a standard database for this kind of
persistent caching. However sorl-thumbnail does ship with a *cached* database
|kvstore|.
.. note:: We have to assume the thumbnail exists if the thumbnail key exists in
the |kvstore|
**There are bonuses**. We can store meta data in the |kvstore| that would be
too costly to retrieve even for local file storage. Today this meta data
consists only of the image size but this could be expanded to for example EXIF
data. The other bonus is that we can keep track of what thumbnails has been
generated from a particular source and deleting them too when the source is
deleted.
`Schematic view of how things are done
`_
.. |kvstore| replace:: Key Value Store
sorl-thumbnail/docs/management.rst 0000644 0001750 0001750 00000004176 13141176077 015767 0 ustar wmb wmb *******************
Management commands
*******************
.. highlight:: python
.. _thumbnail-cleanup:
thumbnail cleanup
=================
``python manage.py thumbnail cleanup``
This cleans up the Key Value Store from stale cache. It removes references to
images that do not exist and thumbnail references and their actual files for
images that do not exist. It removes thumbnails for unknown images.
.. _thumbnail-clear:
thumbnail clear
===============
``python manage.py thumbnail clear``
This totally empties the Key Value Store of all keys that start with the
``settings.THUMBNAIL_KEY_PREFIX``. It does not delete any files. The Key Value
store will update when you hit the template tags, and if the thumbnails files
still exist they will be used and not overwritten/regenerated. This can be
useful if your Key Value Store has garbage data not dealt with by cleanup or
you're switching Key Value Store backend.
.. _thumbnail-clear-delete-referenced:
thumbnail clear_delete_referenced
=================================
``python manage.py thumbnail clear_delete_referenced``
Equivalent to to ``clear`` but first it will delete all thumbnail files
referenced by the Key Value Store. It is generally safe to run this if you do
not reference the generated thumbnails by name somewhere else in your code. As
long as all the original images still exist this will trigger a regeneration of
all the thumbnails the Key Value Store knows about.
.. _thumbnail-clear-delete-all:
thumbnail clear_delete_all
==========================
``python manage.py thumbnail clear_delete_all``
Equivalent to to ``clear`` but afterwards it will delete all thumbnail files
including any orphans not in the Key Value Store. This can be thought of as a
more aggressive version of ``clear_delete_referenced``. Caution should be
exercised with this command if multiple Django sites (as in ``SITE_ID``) or
projects are using the same ``MEDIA_ROOT`` since this will clear out absolutely
everything in the thumbnail cache directory causing thumbnail regeneration for
all sites and projects. When file system storage is used, it is equivalent to
``rm -rf MEDIA_ROOT + THUMBNAIL_PREFIX``
sorl-thumbnail/docs/news.rst 0000644 0001750 0001750 00000002146 13141176077 014622 0 ustar wmb wmb ====
News
====
Announcements
=============
2017-07-07
----------
@Flimm: sorl-thumbnail was welcomed into the `Jazzband organization and project
`__. Jazzband is open to all, and any member of Jazzband
can contribute directly to sorl-thumbnail's GitHub repo. We hope this will
encourage more open source programmers to contribute. Thank you @mariocesar for
taking this step and for the years of effort in this project.
@Flimm: As part of joining Jazzband, the GitHub repo has been moved to
https://github.com/jazzband/sorl-thumbnail
2013-11-12
----------
@mariocesar: We have a new team of mantainers for sorl-thumbnail. I wan't to encourage
all developers that have fixes, forks and new features to talk in the irc channel of
the project. irc://freenode.net/#sorl-thumbnail
2013-11-09
----------
@nikko: Unfortunately I no longer work with Django so my motivation to keep developing
for sorl-thumbnail is very low. If you are interested in taking over this
project please send an email to mikko@aino.se explaing why you are the perfect
fit for owning this project.
.. include:: ../CHANGES.rst
sorl-thumbnail/docs/template.rst 0000644 0001750 0001750 00000021272 13141176077 015462 0 ustar wmb wmb *************************
Template tags and filters
*************************
.. highlight:: html+django
Sorl-thumbnail comes with one template tag `thumbnail`_ and three filters:
`is_portrait`_, `margin`_ and `resolution`_. To use any of them in you
templates you first need to load them::
{% load thumbnail %}
.. _thumbnail:
thumbnail
=========
Syntax::
{% thumbnail source geometry [key1=value1, key2=value2...] as var %}
{% endthumbnail %}
Alternative syntax using empty::
{% thumbnail source geometry [key1=value1, key2=value2...] as var %}
{% empty %}
{% endthumbnail %}
The ``{% empty %}`` section is rendered if the thumbnail source is resolved to
an empty value or an invalid image source, you can think of it as rendering
when the thumbnail becomes undefined.
.. _source:
Source
------
.. highlight:: python
Source can be an ImageField, FileField, a file name (assuming default_storage),
a url. What we need to know is name and storage, see how ImageFile figures
these things out::
from django.utils.encoding import force_text
class ImageFile(BaseImageFile):
_size = None
def __init__(self, file_, storage=None):
if not file_:
raise ThumbnailError('File is empty.')
# figure out name
if hasattr(file_, 'name'):
self.name = file_.name
else:
self.name = force_text(file_)
# figure out storage
if storage is not None:
self.storage = storage
elif hasattr(file_, 'storage'):
self.storage = file_.storage
elif url_pat.match(self.name):
self.storage = UrlStorage()
else:
self.storage = default_storage
Geometry
--------
.. highlight:: html+django
Geometry is specified as ``widthxheight``, ``width`` or ``xheight``.
Width and height are in pixels. Geometry can either be a string or resolve
into a valid geometry string. Examples::
{% thumbnail item.image "200x100" as im %}
{% endthumbnail %}
{% thumbnail item.image "200" as im %}
{% endthumbnail %}
{% thumbnail item.image "x100" as im %}
{% endthumbnail %}
{% thumbnail item.image geometry as im %}
{% endthumbnail %}
If width and height are given the image is rescaled to maximum values of height
and width given. Aspect ratio preserved.
Options
-------
Options are passed on to the backend and engine, the backend generates the
thumbnail filename from it and the engine can use it for processing. Option
keys are not resolved in context but values are. Passing all options to the
engine means that you can easily subclass an engine and create new features
like rounded corners or what ever processing you like. The options described
below are how they are used and interpreted in the shipped engines.
``crop``
^^^^^^^^
This option is only used if both width and height is given. Crop behaves much
like `css background-position`_. The image is first rescaled to minimum values
of height and width given, this will be equivalent to the `padding box` in the
above text. After it is rescaled it will apply the cropping options. There are
some differences to the `css background-position`_:
- Only % and px are valid lengths (units)
- ``noop`` (No Operation) is a valid option which means there is no
cropping after the initial rescaling to minimum of width and height.
There are many overlapping options here for example ``center`` is equivalent to
``50%``. There is not a problem with that in it self but it is a bit of a
problem if you will for sorl-thumbnail. Sorl-thumbnail will generate a new
thumbnail for every unique source, geometry and options. This is a design
choice because we want to stay flexible with the options and not interpret them
anywhere else but in the engine methods. In clear words, be consistent in your
cropping options if you don't want to generate unnecessary thumbnails. In case
you are wondering, sorl-thumbnail sorts the options so the order does not
matter, same options but in different order will generate only one thumbnail.
``upscale``
^^^^^^^^^^^
Upscale is a boolean and controls if the image can be upscaled or not. For
example if your source is 100x100 and you request a thumbnail of size 200x200
and upscale is False this will return a thumbnail of size 100x100. If upscale
was True this would result in a thumbnail size 200x200 (upscaled). The default
value is ``True``.
``quality``
^^^^^^^^^^^
Quality is a value between 0-100 and controls the thumbnail write quality.
Default value is ``95``.
``progressive``
^^^^^^^^^^^^^^^
This controls whether to save jpeg thumbnails as progressive jpegs. Default
value is ``True``.
``orientation``
^^^^^^^^^^^^^^^
This controls whether to orientate the resulting thumbnail with respect to the
source EXIF tags for orientation. Default value is ``True``.
``format``
^^^^^^^^^^
This controls the write format and thumbnail extension. Formats supported by
the shipped engines are ``'JPEG'`` and ``'PNG'``. Default value is ``'JPEG'``.
``colorspace``
^^^^^^^^^^^^^^
This controls the resulting thumbnails color space, valid values are: ``'RGB'``
and ``'GRAY'``. Default value is ``'RGB'``.
``padding``
^^^^^^^^^^^
Padding is a boolean and controls if the image should be padded to fit the
specified geometry.
If your image is ``200x100``::
{% thumbnail image "100x100" padding=True as im %}
``im`` will be ``100x100`` with white padding at the top and bottom. The color
of the padding can be controlled with ``padding_color`` or the setting
``THUMBNAIL_PADDING_COLOR`` which defaults to ``#ffffff``.
Images are not padded by default, but this can be changed by setting
``THUMBNAIL_PADDING`` to ``True``.
``padding_color``
^^^^^^^^^^^^^^^^^
This is the color to use for padding the image. It defaults to ``#ffffff`` and
can be globally set with the setting ``THUMBNAIL_PADDING_COLOR``.
``options``
^^^^^^^^^^^
Yes this option is called ``options``. This needs to be a context variable that
resolves to a dictionary. This dictionary can contain multiple options, for
example::
options = {'colorspace': 'GRAY', 'quality': 75, 'crop': 'center'}
You can use this option together with the other options but beware that the
order will matter. As soon as the keyword ``options`` is encountered all the
options that have a key in ``options`` are overwritten. Similarly, options in
the ``options`` dict will be overwritten by options set after the options
keyword argument to the thumbnail tag.
is_portrait
===========
This filter returns True if the image height is larger than the image width.
Examples::
{% thumbnail item.image "100x100" %}
{% if item.image|is_portrait %}
{% else %}
{% endif %}
{% endthumbnail %}
{% if item.image|is_portrait %}
{% thumbnail item.image "100x200" crop="center" %}
{% endthumbnail %}
{% else %}
{% thumbnail item.image "200x100" crop="center" %}
{% endthumbnail %}
{% endif %}
margin
======
Margin is a filter for calculating margins against a padding box. For example
lets say you have an image ``item.image`` and you want to pad it vertically in
a 1000x1000 box, you would simply write::
The above is a rather synthetic example the more common use case is when you want
boxes of images of a certain size but you do not want to crop them::
{% for profile in profiles %}
{% thumbnail profile.photo "100x100" as im %}
{% empty %}
{% endthumbnail %}
{% enfor %}
The more problematic is to get the top margin, however the margin filter
outputs all values.
.. _css background-position: http://www.w3.org/TR/CSS2/colors.html#propdef-background-position
resolution
==========
Resolution is a filter for obtaining alternative resolution versions of the
thumbnail. Your provided resolution must be one of the
``THUMBNAIL_ALTERNATIVE_RESOLUTIONS`` settings values (default: no alternative resolutions)
For example, let's say you have an image ``item.image`` and you want to
get the 2x DPI version of it. You would simply write::
sorl-thumbnail/docs/examples.rst 0000644 0001750 0001750 00000011542 13141176077 015464 0 ustar wmb wmb ********
Examples
********
Template examples
=================
.. highlight:: html+django
All of the examples assume that you first load the ``thumbnail`` template tag in
your template::
{% load thumbnail %}
Simple::
{% thumbnail item.image "100x100" crop="center" as im %}
{% endthumbnail %}
Crop using margin filter, x, y aliases::
{% thumbnail item.image "100x700" as im %}
{% endthumbnail %}
Using external images and advanced cropping::
{% thumbnail "http://www.aino.se/media/i/logo.png" "40x40" crop="80% top" as im %}
{% endthumbnail %}
Using the empty feature, the empty section is rendered when the source is
resolved to an empty value or an invalid image source, you can think of it as
rendering when the thumbnail becomes undefined::
{% thumbnail item.image my_size_string crop="left" as im %}
{% empty %}
No image
{% endthumbnail %}
Nesting tags and setting size (geometry) for width only::
{% thumbnail item.image "1000" as big %}
{% thumbnail item.image "50x50" crop="center" as small %}
{% endthumbnail %}
{% endthumbnail %}
Setting geometry for height only::
{% thumbnail item.image "x300" as im %}
{% endthumbnail %}
Setting format and using the is_portrait filter::
{% if item.image|is_portrait %}
{% thumbnail item.image "100" crop="10px 10px" format="PNG" as im %}
{% endthumbnail %}
{% else %}
{% thumbnail item.image "50" crop="bottom" format="PNG" as im %}
{% endthumbnail %}
Undefined behaviour
{% endif %}
Using HTML filter::
{{ text|html_thumbnails }}
Using markdown filter::
{{ text|markdown_thumbnails }}
.. highlight:: python
Model examples
==============
Using the ImageField that automatically deletes references to itself in the key
value store and its thumbnail references when deleted::
from django.db import models
from sorl.thumbnail import ImageField
class Item(models.Model):
image = ImageField(upload_to='whatever')
.. note:: You do not need to use the ``sorl.thumbnail.ImageField`` to use
``sorl.thumbnail``. The standard ``django.db.models.ImageField`` is fine
except that using the ``sorl.thumbnail.ImageField`` lets you plugin the
nice admin addition explained in the next section.
Another example on how to use ``sorl.thumbnail.ImageField`` in your existing
project with only small code changes::
# util/models.py
from django.db.models import *
from sorl.thumbnail import ImageField
# myapp/models.py
from util import models
class MyModel(models.Model):
logo = models.ImageField(upload_to='/dev/null')
Admin examples
==============
Recommended usage using ``sorl.thumbnail.admin.AdminImageMixin`` (note that this requires use of ``sorl.thumbnail.ImageField`` in your models as explained above)::
# myapp/admin.py
from django.contrib import admin
from myapp.models import MyModel
from sorl.thumbnail.admin import AdminImageMixin
class MyModelAdmin(AdminImageMixin, admin.ModelAdmin):
pass
And the same thing For inlines::
# myapp/admin.py
from django.contrib import admin
from myapp.models import MyModel, MyInlineModel
from sorl.thumbnail.admin import AdminImageMixin
class MyInlineModelAdmin(AdminImageMixin, admin.TabularInline):
model = MyInlineModel
class MyModelAdmin(admin.ModelAdmin):
inlines = [MyInlineModelAdmin]
Easy to plugin solution example with little code to change::
# util/admin.py
from django.contrib.admin import *
from sorl.thumbnail.admin import AdminImageMixin
class ModelAdmin(AdminImageMixin, ModelAdmin):
pass
class TabularInline(AdminImageMixin, TabularInline):
pass
class StackedInline(AdminImageMixin, StackedInline):
pass
# myapp/admin.py
from util import admin
from myapp.models import MyModel
class MyModelAdmin(admin.ModelAdmin):
pass
Low level API examples
======================
How to get make a thumbnail in your python code::
from sorl.thumbnail import get_thumbnail
im = get_thumbnail(my_file, '100x100', crop='center', quality=99)
How to delete a file, its thumbnails as well as references in the Key Value
Store::
from sorl.thumbnail import delete
delete(my_file)
sorl-thumbnail/docs/requirements.rst 0000644 0001750 0001750 00000006427 13141176077 016377 0 ustar wmb wmb ************
Requirements
************
Base requirements
=================
- `Python`_ 2.7+
- `Django`_
- :ref:`kvstore-requirements`
- :ref:`image-library`
.. _kvstore-requirements:
Key Value Store
===============
sorl-thumbnail needs a Key Value Store for its operation. You can choose between
a **cached database** which requires no special installation to your normal
Django setup besides installing a proper cache like memcached **or** you can
setup **redis** which requires a little bit more work.
Cached DB
---------
All you need to use the cached database key value store is a database and `cache
`_ setup properly using
memcached. This cache needs to be really fast so **using anything else than
memcached is not recomended**.
Redis
-----
Redis is a fast key value store also suited for the job. To use the `redis`_ key
value store you first need to install the `redis server
`_. After that install the `redis client
`_::
pip install redis
.. _image-library:
Image Library
=============
You need to have an image library installed. sorl-thumbnail ships with support
for `Python Imaging Library`_, `pgmagick`_, `ImageMagick`_ (or `GraphicsMagick`)
command line tools. `pgmagick`_ are python bindings for `GraphicsMagick`_
(Magick++)`,
The `ImageMagick`_ based engine ``sorl.thumbnail.engines.convert_engine.Engine``
by default calls ``convert`` and ``identify`` shell commands. You can change the
paths to these tools by setting ``THUMBNAIL_CONVERT`` and ``THUMBNAIL_IDENTIFY``
respectively. Note that youneed to change these to use `GraphicsMagick`_ to
``/path/to/gm convert`` and ``/path/to/gm identify``.
Python Imaging Library installation
-----------------------------------
Prerequisites:
- libjpeg
- zlib
Ubuntu 10.04 package installation::
sudo apt-get install libjpeg62 libjpeg62-dev zlib1g-dev
Installing `Python Imaging Library`_ using pip::
pip install Pillow
Watch the output for messages on what support got compiled in, you at least
want to see the following::
--- JPEG support available
--- ZLIB (PNG/ZIP) support available
pgmagick installation
---------------------
Prerequisites:
- GraphicsMagick
- Boost.Python
Ubuntu 10.04 package installation::
sudo apt-get install libgraphicsmagick++-dev
sudo apt-get install libboost-python1.40-dev
Fedora installation::
yum install GraphicsMagick-c++-devel
yum install boost-devel
Installing `pgmagick`_ using pip::
pip install pgmagick
ImageMagick installation
------------------------
Ubuntu 10.04 package installation::
sudo apt-get install imagemagick
Or if you prefer `GraphicsMagick`_::
sudo apt-get install graphicsmagick
Wand installation
------------------------
Ubuntu installation::
apt-get install libmagickwand-dev
pip install Wand
.. _Python Imaging Library: http://www.pythonware.com/products/pil/
.. _ImageMagick: http://imagemagick.com/
.. _GraphicsMagick: http://www.graphicsmagick.org/
.. _redis: http://code.google.com/p/redis/
.. _redis-py: https://github.com/andymccurdy/redis-py/
.. _Django: http://www.djangoproject.com/
.. _Python: http://www.python.org/
.. _pgmagick: http://bitbucket.org/hhatto/pgmagick/src
.. _wand: http://wand-py.org
sorl-thumbnail/docs/Makefile 0000644 0001750 0001750 00000011012 13141176077 014544 0 ustar wmb wmb # Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
help:
@echo "Please use \`make ' where is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/sorlthumbnail.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/sorlthumbnail.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/sorlthumbnail"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/sorlthumbnail"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
make -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
sorl-thumbnail/docs/logging.rst 0000644 0001750 0001750 00000002164 13141176077 015274 0 ustar wmb wmb ****************
Errors & Logging
****************
.. highlight:: python
Background
==========
When ``THUMBNAIL_DEBUG = False`` errors will be suppressed if they are raised
during rendering the ``thumbnail`` tag or raised within the included filters.
This is the recommended production setting. However it can still be useful to be
notified of those errors. Thus sorl-thumbnail logs errors to a logger and
provides a log handler that sends emails to ``settings.ADMINS``.
How to setup logging
====================
To enable logging you need to add a handler to the 'sorl.thumbnail' logger.
The following example adds the provided handler that sends emails to site admins
in case an error is raised with debugging off::
import logging
from sorl.thumbnail.log import ThumbnailLogHandler
handler = ThumbnailLogHandler()
handler.setLevel(logging.ERROR)
logging.getLogger('sorl.thumbnail').addHandler(handler)
You will need to load this code somewhere in your django project, it could be
in urls.py, settings.py or project/app __init__.py file for example. You could
of course also provide your own logging handler.
sorl-thumbnail/docs/installation.rst 0000644 0001750 0001750 00000001046 13141176077 016345 0 ustar wmb wmb ********************
Installation & Setup
********************
Installation
============
First you need to make sure to read the :doc:`requirements`. To install
sorl-thumbnail is easy::
pip install sorl-thumbnail
Or you can go to `the github page `_
Setup
=====
.. highlight:: python
1. Add ``sorl.thumbnail`` to your ``settings.INSTALLED_APPS``.
2. Configure your ``settings``
3. If you are using the cached database key value store you need to sync the
database::
python manage.py migrate
sorl-thumbnail/docs/contributing.rst 0000644 0001750 0001750 00000005177 13141176077 016364 0 ustar wmb wmb ************
Contributing
************
Feel free to create a new Pull request if you want to propose a new feature
or fix a bug. If you need development support or want to discuss
with other developers, join us in the channel #sorl-thumnbnail at freenode.net
irc://irc.freenode.net/#sorl-thumbnail
Running testsuit
================
For occasional developers we recommend using `Travis CI`_ to run testsuit,
for those who want to run tests locally, read on.
Since sorl-thumbnail supports a variety of image backends, python and
Django versions, we provide an easy way to test locally across all of them.
We use `Vagrant`_ for simple interaction with virtual machines and
`tox`_ for managing python virtual environments.
Some dependencies like pgmagick takes a lot of time to compiling. To speed up your
vagrant box you can edit `Vagrant file`_ with mem and cpu or simply install `vagrant-faster`_.
The resulting .tox folder containing all virtualenvs requires ~
* `Install Vagrant`_
* ``cd`` in your source directory
* Run ``vagrant up`` to prepare VM. It will download Ubuntu image and install all necessary dependencies.
* Run ``vagrant ssh`` to log in the VM
* Launch all tests via ``tox`` (will take some time to build envs first time)
To run only tests against only one configuration use ``-e`` option::
tox -e py34-django16-pil
Py34 stands for python version, 1.6 is Django version and the latter is image library.
For full list of tox environments, see ``tox.ini``
You can get away without using Vagrant if you install all packages locally yourself,
however, this is not recommended.
.. _Travis CI: https://travis-ci.org/jazzband/sorl-thumbnail
.. _Vagrant: http://www.vagrantup.com/
.. _tox: https://testrun.org/tox/latest/
.. _Install Vagrant: http://docs.vagrantup.com/v2/installation/index.html
.. _Vagrant file: https://docs.vagrantup.com/v2/virtualbox/configuration.html
.. _vagrant-faster: https://github.com/rdsubhas/vagrant-faster
Sending pull requests
=====================
1. Fork the repo::
git@github.com:jazzband/sorl-thumbnail.git
2. Create a branch for your specific changes::
$ git checkout master
$ git pull
$ git checkout -b feature/foobar
To simplify things, please, make one branch per issue (pull request).
It's also important to make sure your branch is up-to-date with upstream master,
so that maintainers can merge changes easily.
3. Commit changes. Please update docs, if relevant.
4. Don't forget to run tests to check than nothing breaks.
5. Ideally, write your own tests for new feature/bug fix.
6. Submit a `pull request`_.
.. _pull request: https://help.github.com/articles/using-pull-requests
sorl-thumbnail/docs/reference/ 0000755 0001750 0001750 00000000000 13141176077 015047 5 ustar wmb wmb sorl-thumbnail/docs/reference/settings.rst 0000644 0001750 0001750 00000022204 13141176077 017441 0 ustar wmb wmb ********
Settings
********
.. highlight:: python
``THUMBNAIL_DEBUG``
===================
- Default: ``False``
When set to ``True`` the ``ThumbnailNode.render`` method can raise errors.
Django recommends that tags never raise errors in the ``Node.render`` method
but since sorl-thumbnail is such a complex tag we will need to have more
debugging available.
``THUMBNAIL_BACKEND``
=====================
- Default: ``'sorl.thumbnail.base.ThumbnailBackend'``
This is the entry point for generating thumbnails, you probably want to keep the
default one but just in case you would like to generate thumbnails filenames
differently or need some special functionality you can override this and use
your own implementation.
``THUMBNAIL_KVSTORE``
=====================
- Default: ``'sorl.thumbnail.kvstores.cached_db_kvstore.KVStore'``
sorl-thumbnail needs a Key Value Store to :doc:`/operation`.
sorl-thumbnail ships with support for two Key Value Stores:
Cached DB
---------
``sorl.thumbnail.kvstores.cached_db_kvstore.KVStore``. This is the default and
preferred Key Value Store.
Features
^^^^^^^^
* Fast persistent storage
* First query uses database which is slow. Successive queries are cached and if
you use memcached this is very fast.
* Easy to transfer data between environments since the data is in the default
database.
* If you get the database and fast cache out of sync there could be problems.
Redis
-----
``sorl.thumbnail.kvstores.redis_kvstore.KVStore``. It requires you to install a
Redis server as well as a `redis python client
`_.
Features
^^^^^^^^
* Fast persistent storage
* More dependencies
* Requires a little extra work to transfer data between environments
Dbm
---
``sorl.thumbnail.kvstores.dbm_kvstore.KVStore``. A simple Key Value Store has no
dependencies outside the standard Python library and uses the DBM modules to
store the data.
Features
^^^^^^^^
* No external dependencies, besides the standard library
* No extra components required, e.g., database or cache
* Specially indicated for local development environments
``THUMBNAIL_KEY_DBCOLUMN``
==========================
- Default ``'key'``
Since MSSQL reserved the ``key`` name for db columns you can change this to
something else using this setting.
``THUMBNAIL_ENGINE``
====================
- Default: ``'sorl.thumbnail.engines.pil_engine.Engine'``
This is the processing class for sorl-thumbnail. It does all the resizing,
cropping or whatever processing you want to perform. sorl-thumbnail ships with
three engines:
PIL
---
``'sorl.thumbnail.engines.pil_engine.Engine'``. This is the default engine
because it is what most people have installed already. Features:
* Easy to install
* Produces good quality images but not the best
* It is fast
* Can not handle CMYK sources
Pgmagick
--------
``'sorl.thumbnail.engines.pgmagick_engine.Engine'``. Pgmagick uses `Graphics
`_. Fatures:
* Not easy to install unless on linux, very slow to compile
* Produces high quality images
* It is a tad slow?
* Can handle CMYK sources
ImageMagick / GraphicsMagick
----------------------------
``'sorl.thumbnail.engines.convert_engine.Engine'``. This engine uses the
ImageMagick ``convert`` or GraphicsMagic ``gm convert`` command. Features:
* Easy to install
* Produces high quality images
* It is pretty fast
* Can handle CMYK sources
* It is a command line command, that is less than ideal,
Wand
----------------------------
``'sorl.thumbnail.engines.wand_engine.Engine'``. This engine uses `Wand
`_, a ctypes-based simple ImageMagick binding for Python.
Features:
* Easy to install
* Produces high quality images
* Can handle CMYK sources
* Works on Python 2.6, 2.7, 3.2, 3.3, and PyPy
``THUMBNAIL_CONVERT``
=====================
- Default ``'convert'``
Path to convert command, use ``'gm convert'`` for GraphicsMagick.
Only applicable for the convert Engine.
``THUMBNAIL_IDENTIFY``
======================
- Default ``'identify'``
Path to identify command, use ``'gm identify'`` for GraphicsMagick.
Only applicable for the convert Engine.
``THUMBNAIL_STORAGE``
=====================
- Default: ``settings.DEFAULT_FILE_STORAGE``
The storage class to use for the generated thumbnails.
``THUMBNAIL_REDIS_DB``
======================
- Default: ``0``
The Redis database. Only applicable for the Redis Key Value Store
``THUMBNAIL_REDIS_PASSWORD``
============================
- Default: ``''``
The password for Redis server. Only applicable for the Redis Key Value Store
``THUMBNAIL_REDIS_HOST``
========================
- Default: ``'localhost'``
The host for Redis server. Only applicable for the Redis Key Value Store
``THUMBNAIL_REDIS_PORT``
========================
- Default: ``6379``
The port for Redis server. Only applicable for the Redis Key Value Store
``THUMBNAIL_DBM_FILE``
======================
- Default: ``thumbnail_kvstore``
Filename of the DBM database. Depending on the DBM engine selected by your
Python installation, this will be used as a prefix because multiple files may be
created. This can be an absolute path.
``THUMBNAIL_DBM_MODE``
======================
- Default: ``0x644``
Permission bits to use when creating new DBM files
``THUMBNAIL_CACHE_TIMEOUT``
===========================
- Default: ``3600 * 24 * 365 * 10``
Cache timeout for Cached DB Key Value Store in seconds. You should probably keep this
at maximum or ``None`` if your caching backend can handle that as infinite.
Only applicable for the Cached DB Key Value Store.
``THUMBNAIL_CACHE``
===================
- Default: ``'default'``
Cache configuration for Cached DB Key Value Store. Defaults to the ``'default'`` cache
but some applications might have multiple cache clusters.
``THUMBNAIL_KEY_PREFIX``
========================
- Default: ``'sorl-thumbnail'``
Key prefix used by the key value store.
``THUMBNAIL_PREFIX``
====================
- Default: ``'cache/'``
The generated thumbnails filename prefix.
``THUMBNAIL_FORMAT``
====================
- Default: ``'JPEG'``
Default image format, supported formats are: ``'JPEG'``, ``'PNG'``. This also implicitly
sets the filename extension. This can be overridden by individual options.
``THUMBNAIL_PRESERVE_FORMAT``
=============================
- Default: ``False``
If ``True``, the format of the input file will be preserved. If ``False``,
``THUMBNAIL_FORMAT`` will be used.
``THUMBNAIL_COLORSPACE``
========================
- Default: ``'RGB'``
Default thumbnail color space, engines are required to implement: ``'RGB'``,
``'GRAY'`` Setting this to None will keep the original color space. This can be
overridden by individual options.
``THUMBNAIL_UPSCALE``
=====================
- Default: ``True``
Should we upscale by default? ``True`` means we upscale images by default.
``False`` means we don't. This can be overridden by individual options.
``THUMBNAIL_QUALITY``
=====================
- Default: ``95``
Default thumbnail quality. A value between 0 and 100 is allowed. This can be
overridden by individual options.
``THUMBNAIL_PROGRESSIVE``
=========================
- Default: ``True``
Saves jpeg thumbnails as progressive jpegs. This can be overridden by individual
options.
``THUMBNAIL_ORIENTATION``
=========================
- Default: ``True``
Orientate the thumbnail with respect to source EXIF orientation tag
``THUMBNAIL_DUMMY``
===================
- Default: ``False``
This is a very powerful option which came from real world frustration. The use
case is when you want to do development on a deployed project that has image
references in its database. Instead of downloading all the image files from the
server hosting the deployed project and all its thumbnails we just set this
option to ``True``. This will generate placeholder images for all thumbnails
missing input source.
``THUMBNAIL_DUMMY_SOURCE``
==========================
- Default ``http://dummyimage.com/%(width)sx%(height)s``
This is the generated thumbnail whensource of the presented thumbnail. Width and
Height is passed to the string for formatting. Other options are for example:
- ``http://placehold.it/%(width)sx%(height)s``
- ``http://placekitten.com/%(width)s/%(height)s``
``THUMBNAIL_DUMMY_RATIO``
=========================
- Default: ``1.5``
This value sets an image ratio to all thumbnails that are not defined by width
**and** height since we cannot determine from the file input (since we don't
have that).
``THUMBNAIL_ALTERNATIVE_RESOLUTIONS``
=====================================
- Default: ``[]``
- Example: ``[1.5, 2]``
This value enables creation of additional high-resolution ("Retina") thumbnails
for every thumbnail. Resolution multiplicators, e.g. value 2 means for every thumbnail
of regular size x\*y, additional thumbnail of 2x\*2y size is created.
``THUMBNAIL_FILTER_WIDTH``
==========================
- Default: ``500``
This value sets the width of thumbnails inserted when running filters one texts
that regex replaces references to images with thumbnails.
``THUMBNAIL_URL_TIMEOUT``
=========================
- Default: ``None``
This value sets the timeout value in seconds when retrieving a source image from a URL.
If no timeout value is specified, it will wait indefinitely for a response.
sorl-thumbnail/docs/reference/index.rst 0000644 0001750 0001750 00000000123 13141176077 016704 0 ustar wmb wmb *********
Reference
*********
.. toctree::
:maxdepth: 2
image
settings
sorl-thumbnail/docs/reference/image.rst 0000644 0001750 0001750 00000003617 13141176077 016672 0 ustar wmb wmb *********
ImageFile
*********
.. highlight:: html+django
``ImageFile`` is an image abstraction that contains useful attributes when
working with images. The ``thumbnail`` template tag puts the generated thumbnail
in context as an ``ImageFile`` instance. In the following example::
{% thumbnail item.image "100x100" as im %}
{% endthumbnail %}
``im`` will be an ``ImageFile`` instance.
.. highlight:: python
ImageFile attributes
====================
``name``
--------
Name of the image as returned from the underlaying storage.
``storage``
-----------
Returns the storage instance.
``width``
---------
Returns the width of the image in pixels.
``x``
-----
Alias of ``width``
``height``
----------
Returns the height of the image in pixels.
``y``
-----
Alias of ``width``
``ratio``
---------
Returns the image ratio (y/x) as a float
``url``
-------
URL of the image url as returned by the underlaying storage.
``src``
-------
Alias of ``url``
``size``
--------
Returns the image size in pixels as a (x, y) tuple
``key``
-------
Returns a unique key based on ``name`` and ``storage``.
ImageFile methods
=================
``exists``
----------
Returns whether the file exists as returned by the underlaying storage.
``is_portrait``
---------------
Returns ``True`` if ``y > x``, else ``False``
``set_size``
------------
Sets the size of the image, takes an optional size tuple (x, y) as argument.
``read``
--------
Reads the file as done from the underlaying storage.
``write``
---------
Writes content to the file. Takes content as argument. Content is either raw
data or an instance of ``django.core.files.base.ContentFile``.
``delete``
----------
Deletes the file from underlaying storage.
``serialize``
-------------
Returns a serialized version of self.
``serialize_storage``
---------------------
Returns the ``self.storage`` as a serialized dot name path string.
sorl-thumbnail/docs/conf.py 0000644 0001750 0001750 00000016457 13141176077 014425 0 ustar wmb wmb # -*- coding: utf-8 -*-
#
# sorl-thumbnail documentation build configuration file, created by
# sphinx-quickstart on Fri Nov 12 00:51:21 2010.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import os
import sphinx
import sys
sys.path.insert(0, os.path.pardir)
import sorl
for j in xrange(0, len(sphinx.__version__)):
try:
version = float(sphinx.__version__[:-j])
break
except ValueError:
pass
version = 0
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
if version < 1.0:
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage']
else:
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'sorl-thumbnail'
copyright = u'2010, Mikko Hellsing'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = sorl.__version__
# The full version, including alpha/beta/rc tags.
release = sorl.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = ['_theme']
# The name for this set of Sphinx documents. If None, it defaults to
# " v documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'sorlthumbnaildoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'sorlthumbnail.tex', u'sorl-thumbnail Documentation',
u'Mikko Hellsing', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'sorlthumbnail', u'sorl-thumbnail Documentation',
[u'Mikko Hellsing'], 1)
]
sorl-thumbnail/LICENSE 0000644 0001750 0001750 00000002756 13141176077 013200 0 ustar wmb wmb Copyright (c) 2010, Mikko Hellsing
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the sorl-thumbnail the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
sorl-thumbnail/sorl/ 0000755 0001750 0001750 00000000000 13141176077 013140 5 ustar wmb wmb sorl-thumbnail/sorl/thumbnail/ 0000755 0001750 0001750 00000000000 13141176077 015123 5 ustar wmb wmb sorl-thumbnail/sorl/thumbnail/engines/ 0000755 0001750 0001750 00000000000 13141176077 016553 5 ustar wmb wmb sorl-thumbnail/sorl/thumbnail/engines/wand_engine.py 0000644 0001750 0001750 00000004751 13141176077 021412 0 ustar wmb wmb '''
Wand (>=v0.3.0) engine for Sorl-thumbnail
'''
from __future__ import unicode_literals
from wand.image import Image
from wand import exceptions
from sorl.thumbnail.engines.base import EngineBase
class Engine(EngineBase):
def get_image(self, source):
return Image(blob=source.read())
def get_image_size(self, image):
return image.size
def is_valid_image(self, raw_data):
'''
Wand library makes sure when opening any image that is fine, when
the image is corrupted raises an exception.
'''
try:
Image(blob=raw_data)
return True
except (exceptions.CorruptImageError, exceptions.MissingDelegateError):
return False
def _orientation(self, image):
orientation = image.orientation
if orientation == 'top_right':
image.flop()
elif orientation == 'bottom_right':
image.rotate(degree=180)
elif orientation == 'bottom_left':
image.flip()
elif orientation == 'left_top':
image.rotate(degree=90)
image.flop()
elif orientation == 'right_top':
image.rotate(degree=90)
elif orientation == 'right_bottom':
image.rotate(degree=-90)
image.flop()
elif orientation == 'left_bottom':
image.rotate(degree=-90)
image.orientation = 'top_left'
return image
def _flip_dimensions(self, image):
return image.orientation in ['left_top', 'right_top', 'right_bottom', 'left_bottom']
def _colorspace(self, image, colorspace):
if colorspace == 'RGB':
if image.alpha_channel:
image.type = 'truecolormatte'
else:
image.type = 'truecolor'
elif colorspace == 'GRAY':
if image.alpha_channel:
image.type = 'grayscalematte'
else:
image.type = 'grayscale'
else:
return image
return image
def _scale(self, image, width, height):
image.resize(width, height)
return image
def _crop(self, image, width, height, x_offset, y_offset):
image.crop(x_offset, y_offset, width=width, height=height)
return image
def _get_raw_data(self, image, format_, quality, image_info=None, progressive=False):
image.compression_quality = quality
if format_ == 'JPEG' and progressive:
image.format = 'pjpeg'
return image.make_blob()
sorl-thumbnail/sorl/thumbnail/engines/pil_engine.py 0000644 0001750 0001750 00000021763 13141176077 021247 0 ustar wmb wmb from __future__ import unicode_literals, division
import math
from sorl.thumbnail.engines.base import EngineBase
from sorl.thumbnail.compat import BufferIO
try:
from PIL import Image, ImageFile, ImageDraw, ImageFilter
except ImportError:
import Image
import ImageFile
import ImageDraw
EXIF_ORIENTATION = 0x0112
def round_corner(radius, fill):
"""Draw a round corner"""
corner = Image.new('L', (radius, radius), 0) # (0, 0, 0, 0))
draw = ImageDraw.Draw(corner)
draw.pieslice((0, 0, radius * 2, radius * 2), 180, 270, fill=fill)
return corner
def round_rectangle(size, radius, fill):
"""Draw a rounded rectangle"""
width, height = size
rectangle = Image.new('L', size, 255) # fill
corner = round_corner(radius, 255) # fill
rectangle.paste(corner, (0, 0))
rectangle.paste(corner.rotate(90),
(0, height - radius)) # Rotate the corner and paste it
rectangle.paste(corner.rotate(180), (width - radius, height - radius))
rectangle.paste(corner.rotate(270), (width - radius, 0))
return rectangle
class GaussianBlur(ImageFilter.Filter):
name = "GaussianBlur"
def __init__(self, radius=2):
self.radius = radius
def filter(self, image):
return image.gaussian_blur(self.radius)
class Engine(EngineBase):
def get_image(self, source):
buffer = BufferIO(source.read())
return Image.open(buffer)
def get_image_size(self, image):
return image.size
def get_image_info(self, image):
return image.info or {}
def is_valid_image(self, raw_data):
buffer = BufferIO(raw_data)
try:
trial_image = Image.open(buffer)
trial_image.verify()
except Exception:
return False
return True
def _cropbox(self, image, x, y, x2, y2):
return image.crop((x, y, x2, y2))
def _orientation(self, image):
try:
exif = image._getexif()
except:
exif = None
if exif:
orientation = exif.get(EXIF_ORIENTATION)
if orientation == 2:
image = image.transpose(Image.FLIP_LEFT_RIGHT)
elif orientation == 3:
image = image.rotate(180)
elif orientation == 4:
image = image.transpose(Image.FLIP_TOP_BOTTOM)
elif orientation == 5:
image = image.rotate(-90, expand=1).transpose(Image.FLIP_LEFT_RIGHT)
elif orientation == 6:
image = image.rotate(-90, expand=1)
elif orientation == 7:
image = image.rotate(90, expand=1).transpose(Image.FLIP_LEFT_RIGHT)
elif orientation == 8:
image = image.rotate(90, expand=1)
return image
def _flip_dimensions(self, image):
try:
exif = image._getexif()
except (AttributeError, IOError, KeyError, IndexError):
exif = None
if exif:
orientation = exif.get(0x0112)
return orientation in [5, 6, 7, 8]
return False
def _colorspace(self, image, colorspace):
if colorspace == 'RGB':
if image.mode == 'RGBA':
return image # RGBA is just RGB + Alpha
if image.mode == 'LA' or (image.mode == 'P' and 'transparency' in image.info):
newimage = image.convert('RGBA')
transparency = image.info.get('transparency')
if transparency is not None:
mask = image.convert('RGBA').split()[-1]
newimage.putalpha(mask)
return newimage
return image.convert('RGB')
if colorspace == 'GRAY':
return image.convert('L')
return image
def _remove_border(self, image, image_width, image_height):
borders = {
'top': lambda iy, dy, y: (dy, dy + y),
'right': lambda ix, dx, x: (ix - dx - x, ix - dx),
'bottom': lambda iy, dy, y: (iy - dy - y, iy - dy),
'left': lambda ix, dx, x: (dx, dx + x),
}
offset = {'top': 0, 'right': 0, 'bottom': 0, 'left': 0, }
for border in ['top', 'bottom']:
# Don't remove too much, the image may just be plain
while offset[border] < image_height / 3.5:
slice_size = min(image_width / 20, 10)
y_range = borders[border](image_height, offset[border], slice_size)
section = image.crop((0, y_range[0], image_width, y_range[1]))
# If this section is below the threshold; remove it
if self._get_image_entropy(section) < 2.0:
offset[border] += slice_size
else:
break
for border in ['left', 'right']:
while offset[border] < image_width / 3.5:
slice_size = min(image_height / 20, 10)
x_range = borders[border](image_width, offset[border], slice_size)
section = image.crop((x_range[0], 0, x_range[1], image_height))
if self._get_image_entropy(section) < 2.0:
offset[border] += slice_size
else:
break
return image.crop((offset['left'], offset['top'], image_width - offset['right'],
image_height - offset['bottom']))
# Credit to chrisopherhan https://github.com/christopherhan/pycrop
# This is just a slight rework of pycrops implimentation
def _entropy_crop(self, image, geometry_width, geometry_height, image_width, image_height):
geometry_ratio = geometry_width / geometry_height
# The is proportionally wider than it should be
while image_width / image_height > geometry_ratio:
slice_width = max(image_width - geometry_width, 10)
right = image.crop((image_width - slice_width, 0, image_width, image_height))
left = image.crop((0, 0, slice_width, image_height))
if self._get_image_entropy(left) < self._get_image_entropy(right):
image = image.crop((slice_width, 0, image_width, image_height))
else:
image = image.crop((0, 0, image_height - slice_width, image_height))
image_width -= slice_width
# The image is proportionally taller than it should be
while image_width / image_height < geometry_ratio:
slice_height = min(image_height - geometry_height, 10)
bottom = image.crop((0, image_height - slice_height, image_width, image_height))
top = image.crop((0, 0, image_width, slice_height))
if self._get_image_entropy(bottom) < self._get_image_entropy(top):
image = image.crop((0, 0, image_width, image_height - slice_height))
else:
image = image.crop((0, slice_height, image_width, image_height))
image_height -= slice_height
return image
def _scale(self, image, width, height):
return image.resize((width, height), resample=Image.ANTIALIAS)
def _crop(self, image, width, height, x_offset, y_offset):
return image.crop((x_offset, y_offset,
width + x_offset, height + y_offset))
def _rounded(self, image, r):
i = round_rectangle(image.size, r, "notusedblack")
image.putalpha(i)
return image
def _blur(self, image, radius):
return image.filter(GaussianBlur(radius))
def _padding(self, image, geometry, options):
x_image, y_image = self.get_image_size(image)
left = int((geometry[0] - x_image) / 2)
top = int((geometry[1] - y_image) / 2)
color = options.get('padding_color')
im = Image.new(image.mode, geometry, color)
im.paste(image, (left, top))
return im
def _get_raw_data(self, image, format_, quality, image_info=None, progressive=False):
# Increase (but never decrease) PIL buffer size
ImageFile.MAXBLOCK = max(ImageFile.MAXBLOCK, image.size[0] * image.size[1])
bf = BufferIO()
params = {
'format': format_,
'quality': quality,
'optimize': 1,
}
# keeps icc_profile
if 'icc_profile' in image_info:
params['icc_profile'] = image_info['icc_profile']
raw_data = None
if format_ == 'JPEG' and progressive:
params['progressive'] = True
try:
# Do not save unnecessary exif data for smaller thumbnail size
params.pop('exif', {})
image.save(bf, **params)
except (IOError, OSError):
# Try without optimization.
params.pop('optimize')
image.save(bf, **params)
else:
raw_data = bf.getvalue()
finally:
bf.close()
return raw_data
def _get_image_entropy(self, image):
"""calculate the entropy of an image"""
hist = image.histogram()
hist_size = sum(hist)
hist = [float(h) / hist_size for h in hist]
return -sum([p * math.log(p, 2) for p in hist if p != 0])
sorl-thumbnail/sorl/thumbnail/engines/vipsthumbnail_engine.py 0000644 0001750 0001750 00000007566 13141176077 023355 0 ustar wmb wmb from __future__ import unicode_literals, with_statement
import re
import os
import subprocess
from collections import OrderedDict
from django.utils.encoding import smart_str
from django.core.files.temp import NamedTemporaryFile
from sorl.thumbnail.base import EXTENSIONS
from sorl.thumbnail.conf import settings
from sorl.thumbnail.engines.base import EngineBase
size_re = re.compile(r'^(?:.+) (?P\d+)x(?P\d+)')
class Engine(EngineBase):
"""
Image object is a dict with source path, options and size
"""
def write(self, image, options, thumbnail):
"""
Writes the thumbnail image
"""
args = settings.THUMBNAIL_VIPSTHUMBNAIL.split(' ')
args.append(image['source'])
for k in image['options']:
v = image['options'][k]
args.append('--%s' % k)
if v is not None:
args.append('%s' % v)
suffix = '.%s' % EXTENSIONS[options['format']]
write_options = []
if options['format'] == 'JPEG' and options.get(
'progressive', settings.THUMBNAIL_PROGRESSIVE):
write_options.append("interlace")
if options['quality']:
if options['format'] == 'JPEG':
write_options.append("Q=%d" % options['quality'])
with NamedTemporaryFile(suffix=suffix, mode='rb') as fp:
# older vipsthumbails used -o, this was renamed to -f in 8.0, use
# -o here for commpatibility
args.append("-o")
args.append(fp.name + "[%s]" % ",".join(write_options))
args = map(smart_str, args)
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.wait()
out, err = p.communicate()
if err:
raise Exception(err)
thumbnail.write(fp.read())
def cleanup(self, image):
os.remove(image['source']) # we should not need this now
def get_image(self, source):
"""
Returns the backend image objects from a ImageFile instance
"""
with NamedTemporaryFile(mode='wb', delete=False) as fp:
fp.write(source.read())
return {'source': fp.name, 'options': OrderedDict(), 'size': None}
def get_image_size(self, image):
"""
Returns the image width and height as a tuple
"""
if image['size'] is None:
args = settings.THUMBNAIL_VIPSHEADER.split(' ')
args.append(image['source'])
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.wait()
m = size_re.match(str(p.stdout.read()))
image['size'] = int(m.group('x')), int(m.group('y'))
return image['size']
def is_valid_image(self, raw_data):
"""
vipsheader will try a lot of formats, including all those supported by
imagemagick if compiled with magick support, this can take a while
"""
with NamedTemporaryFile(mode='wb') as fp:
fp.write(raw_data)
fp.flush()
args = settings.THUMBNAIL_VIPSHEADER.split(' ')
args.append(fp.name)
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
retcode = p.wait()
return retcode == 0
def _orientation(self, image):
# vipsthumbnail also corrects the orientation exif data for
# destination
image['options']['rotate'] = None
return image
def _colorspace(self, image, colorspace):
"""
vipsthumbnail does not support greyscaling of images, but pretend it
does
"""
return image
def _scale(self, image, width, height):
"""
Does the resizing of the image
"""
image['options']['size'] = '%sx%s' % (width, height)
image['size'] = (width, height) # update image size
return image
sorl-thumbnail/sorl/thumbnail/engines/__init__.py 0000644 0001750 0001750 00000000000 13141176077 020652 0 ustar wmb wmb sorl-thumbnail/sorl/thumbnail/engines/convert_engine.py 0000644 0001750 0001750 00000015165 13141176077 022142 0 ustar wmb wmb from __future__ import unicode_literals, with_statement
import re
import os
import subprocess
import logging
from collections import OrderedDict
from django.utils.encoding import smart_str
from django.core.files.temp import NamedTemporaryFile
from sorl.thumbnail.base import EXTENSIONS
from sorl.thumbnail.compat import b
from sorl.thumbnail.conf import settings
from sorl.thumbnail.engines.base import EngineBase
logger = logging.getLogger(__name__)
size_re = re.compile(r'^(?:.+) (?:[A-Z]+) (?P\d+)x(?P\d+)')
class Engine(EngineBase):
"""
Image object is a dict with source path, options and size
"""
def write(self, image, options, thumbnail):
"""
Writes the thumbnail image
"""
if options['format'] == 'JPEG' and options.get(
'progressive', settings.THUMBNAIL_PROGRESSIVE):
image['options']['interlace'] = 'line'
image['options']['quality'] = options['quality']
args = settings.THUMBNAIL_CONVERT.split(' ')
args.append(image['source'] + '[0]')
for k in image['options']:
v = image['options'][k]
args.append('-%s' % k)
if v is not None:
args.append('%s' % v)
flatten = "on"
if 'flatten' in options:
flatten = options['flatten']
if settings.THUMBNAIL_FLATTEN and not flatten == "off":
args.append('-flatten')
suffix = '.%s' % EXTENSIONS[options['format']]
with NamedTemporaryFile(suffix=suffix, mode='rb') as fp:
args.append(fp.name)
args = map(smart_str, args)
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
returncode = p.wait()
out, err = p.communicate()
if returncode:
raise EngineError(
"The command %r exited with a non-zero exit code and printed this to stderr: %s"
% (args, err)
)
elif err:
logger.error("Captured stderr: %s", err)
thumbnail.write(fp.read())
def cleanup(self, image):
os.remove(image['source']) # we should not need this now
def get_image(self, source):
"""
Returns the backend image objects from a ImageFile instance
"""
with NamedTemporaryFile(mode='wb', delete=False) as fp:
fp.write(source.read())
return {'source': fp.name, 'options': OrderedDict(), 'size': None}
def get_image_size(self, image):
"""
Returns the image width and height as a tuple
"""
if image['size'] is None:
args = settings.THUMBNAIL_IDENTIFY.split(' ')
args.append(image['source'])
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.wait()
m = size_re.match(str(p.stdout.read()))
image['size'] = int(m.group('x')), int(m.group('y'))
return image['size']
def is_valid_image(self, raw_data):
"""
This is not very good for imagemagick because it will say anything is
valid that it can use as input.
"""
with NamedTemporaryFile(mode='wb') as fp:
fp.write(raw_data)
fp.flush()
args = settings.THUMBNAIL_IDENTIFY.split(' ')
args.append(fp.name)
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
retcode = p.wait()
return retcode == 0
def _orientation(self, image):
# return image
# XXX need to get the dimensions right after a transpose.
if settings.THUMBNAIL_CONVERT.endswith('gm convert'):
args = settings.THUMBNAIL_IDENTIFY.split()
args.extend(['-format', '%[exif:orientation]', image['source']])
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.wait()
result = p.stdout.read().strip()
if result and result != b('unknown'):
result = int(result)
options = image['options']
if result == 2:
options['flop'] = None
elif result == 3:
options['rotate'] = '180'
elif result == 4:
options['flip'] = None
elif result == 5:
options['rotate'] = '90'
options['flop'] = None
elif result == 6:
options['rotate'] = '90'
elif result == 7:
options['rotate'] = '-90'
options['flop'] = None
elif result == 8:
options['rotate'] = '-90'
else:
# ImageMagick also corrects the orientation exif data for
# destination
image['options']['auto-orient'] = None
return image
def _flip_dimensions(self, image):
if settings.THUMBNAIL_CONVERT.endswith('gm convert'):
args = settings.THUMBNAIL_IDENTIFY.split()
args.extend(['-format', '%[exif:orientation]', image['source']])
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.wait()
result = p.stdout.read().strip()
return result and result != 'unknown' and int(result) in [5, 6, 7, 8]
else:
return False
def _colorspace(self, image, colorspace):
"""
`Valid colorspaces
`_.
Backends need to implement the following::
RGB, GRAY
"""
image['options']['colorspace'] = colorspace
return image
def _crop(self, image, width, height, x_offset, y_offset):
"""
Crops the image
"""
image['options']['crop'] = '%sx%s+%s+%s' % (width, height, x_offset, y_offset)
image['size'] = (width, height) # update image size
return image
def _scale(self, image, width, height):
"""
Does the resizing of the image
"""
image['options']['scale'] = '%sx%s!' % (width, height)
image['size'] = (width, height) # update image size
return image
def _padding(self, image, geometry, options):
"""
Pads the image
"""
# The order is important. The gravity option should come before extent.
image['options']['background'] = options.get('padding_color')
image['options']['gravity'] = 'center'
image['options']['extent'] = '%sx%s' % (geometry[0], geometry[1])
return image
class EngineError(Exception):
pass
sorl-thumbnail/sorl/thumbnail/engines/base.py 0000644 0001750 0001750 00000017635 13141176077 020053 0 ustar wmb wmb # coding=utf-8
from __future__ import division
from sorl.thumbnail.conf import settings
from sorl.thumbnail.helpers import toint
from sorl.thumbnail.parsers import parse_crop
from sorl.thumbnail.parsers import parse_cropbox
class EngineBase(object):
"""
ABC for Thumbnail engines, methods are static
"""
def create(self, image, geometry, options):
"""
Processing conductor, returns the thumbnail as an image engine instance
"""
image = self.cropbox(image, geometry, options)
image = self.orientation(image, geometry, options)
image = self.colorspace(image, geometry, options)
image = self.remove_border(image, options)
image = self.scale(image, geometry, options)
image = self.crop(image, geometry, options)
image = self.rounded(image, geometry, options)
image = self.blur(image, geometry, options)
image = self.padding(image, geometry, options)
return image
def cropbox(self, image, geometry, options):
"""
Wrapper for ``_cropbox``
"""
cropbox = options['cropbox']
if not cropbox:
return image
x, y, x2, y2 = parse_cropbox(cropbox)
return self._cropbox(image, x, y, x2, y2)
def orientation(self, image, geometry, options):
"""
Wrapper for ``_orientation``
"""
if options.get('orientation', settings.THUMBNAIL_ORIENTATION):
return self._orientation(image)
self.reoriented = True
return image
def flip_dimensions(self, image, geometry=None, options=None):
options = options or {}
reoriented = hasattr(self, 'reoriented')
if options.get('orientation', settings.THUMBNAIL_ORIENTATION) and not reoriented:
return self._flip_dimensions(image)
return False
def colorspace(self, image, geometry, options):
"""
Wrapper for ``_colorspace``
"""
colorspace = options['colorspace']
return self._colorspace(image, colorspace)
def remove_border(self, image, options):
if options.get('remove_border', False):
x_image, y_image = self.get_image_size(image)
image = self._remove_border(image, x_image, y_image)
return image
def _calculate_scaling_factor(self, x_image, y_image, geometry, options):
crop = options['crop']
factors = (geometry[0] / x_image, geometry[1] / y_image)
return max(factors) if crop else min(factors)
def scale(self, image, geometry, options):
"""
Wrapper for ``_scale``
"""
upscale = options['upscale']
x_image, y_image = map(float, self.get_image_size(image))
factor = self._calculate_scaling_factor(x_image, y_image, geometry, options)
if factor < 1 or upscale:
width = toint(x_image * factor)
height = toint(y_image * factor)
image = self._scale(image, width, height)
return image
def crop(self, image, geometry, options):
"""
Wrapper for ``_crop``
"""
crop = options['crop']
x_image, y_image = self.get_image_size(image)
if not crop or crop == 'noop':
return image
elif crop == 'smart':
# Smart cropping is suitably different from regular cropping
# to warrent it's own function
return self._entropy_crop(image, geometry[0], geometry[1], x_image, y_image)
# Handle any other crop option with the backend crop function.
geometry = (min(x_image, geometry[0]), min(y_image, geometry[1]))
x_offset, y_offset = parse_crop(crop, (x_image, y_image), geometry)
return self._crop(image, geometry[0], geometry[1], x_offset, y_offset)
def rounded(self, image, geometry, options):
"""
Wrapper for ``_rounded``
"""
r = options['rounded']
if not r:
return image
return self._rounded(image, int(r))
def blur(self, image, geometry, options):
"""
Wrapper for ``_blur``
"""
if options.get('blur'):
return self._blur(image, int(options.get('blur')))
return image
def padding(self, image, geometry, options):
"""
Wrapper for ``_padding``
"""
if options.get('padding') and self.get_image_size(image) != geometry:
return self._padding(image, geometry, options)
return image
def write(self, image, options, thumbnail):
"""
Wrapper for ``_write``
"""
format_ = options['format']
quality = options['quality']
image_info = options.get('image_info', {})
# additional non-default-value options:
progressive = options.get('progressive', settings.THUMBNAIL_PROGRESSIVE)
raw_data = self._get_raw_data(
image, format_, quality,
image_info=image_info,
progressive=progressive
)
thumbnail.write(raw_data)
def cleanup(self, image):
"""Some backends need to manually cleanup after thumbnails are created"""
pass
def get_image_ratio(self, image, options):
"""
Calculates the image ratio. If cropbox option is used, the ratio
may have changed.
"""
cropbox = options['cropbox']
if cropbox:
x, y, x2, y2 = parse_cropbox(cropbox)
x = x2 - x
y = y2 - y
else:
x, y = self.get_image_size(image)
return float(x) / y
def get_image_info(self, image):
"""
Returns metadata of an ImageFile instance
"""
return {}
# Methods which engines need to implement
# The ``image`` argument refers to a backend image object
def get_image(self, source):
"""
Returns the backend image objects from an ImageFile instance
"""
raise NotImplementedError()
def get_image_size(self, image):
"""
Returns the image width and height as a tuple
"""
raise NotImplementedError()
def is_valid_image(self, raw_data):
"""
Checks if the supplied raw data is valid image data
"""
raise NotImplementedError()
def _orientation(self, image):
"""
Read orientation exif data and orientate the image accordingly
"""
return image
def _colorspace(self, image, colorspace):
"""
`Valid colorspaces
`_.
Backends need to implement the following::
RGB, GRAY
"""
raise NotImplementedError()
def _remove_border(self, image, image_width, image_height):
"""
Remove borders around images
"""
raise NotImplementedError()
def _entropy_crop(self, image, geometry_width, geometry_height, image_width, image_height):
"""
Crop the image to the correct aspect ratio
by removing the lowest entropy parts
"""
raise NotImplementedError()
def _scale(self, image, width, height):
"""
Does the resizing of the image
"""
raise NotImplementedError()
def _crop(self, image, width, height, x_offset, y_offset):
"""
Crops the image
"""
raise NotImplementedError()
def _get_raw_data(self, image, format_, quality, image_info=None, progressive=False):
"""
Gets raw data given the image, format and quality. This method is
called from :meth:`write`
"""
raise NotImplementedError()
def _padding(self, image, geometry, options):
"""
Pads the image
"""
raise NotImplementedError()
def _cropbox(self, image, x, y, x2, y2):
raise NotImplementedError()
def _rounded(self, image, r):
raise NotImplementedError()
def _blur(self, image, radius):
raise NotImplementedError()
sorl-thumbnail/sorl/thumbnail/engines/pgmagick_engine.py 0000644 0001750 0001750 00000005750 13141176077 022243 0 ustar wmb wmb from __future__ import unicode_literals
from pgmagick import Blob, Geometry, Image, ImageType
from pgmagick import InterlaceType, OrientationType
from sorl.thumbnail.engines.base import EngineBase
try:
from pgmagick._pgmagick import get_blob_data
except ImportError:
from base64 import b64decode
def get_blob_data(blob):
return b64decode(blob.base64())
class Engine(EngineBase):
def get_image(self, source):
blob = Blob()
blob.update(source.read())
return Image(blob)
def get_image_size(self, image):
geometry = image.size()
return geometry.width(), geometry.height()
def is_valid_image(self, raw_data):
blob = Blob()
blob.update(raw_data)
im = Image(blob)
return im.isValid()
def _cropbox(self, image, x, y, x2, y2):
geometry = Geometry(x2 - x, y2 - y, x, y)
image.crop(geometry)
return image
def _orientation(self, image):
orientation = image.orientation()
if orientation == OrientationType.TopRightOrientation:
image.flop()
elif orientation == OrientationType.BottomRightOrientation:
image.rotate(180)
elif orientation == OrientationType.BottomLeftOrientation:
image.flip()
elif orientation == OrientationType.LeftTopOrientation:
image.rotate(90)
image.flop()
elif orientation == OrientationType.RightTopOrientation:
image.rotate(90)
elif orientation == OrientationType.RightBottomOrientation:
image.rotate(-90)
image.flop()
elif orientation == OrientationType.LeftBottomOrientation:
image.rotate(-90)
image.orientation(OrientationType.TopLeftOrientation)
return image
def flip_dimensions(self, image):
return image.orientation() in [
OrientationType.LeftTopOrientation,
OrientationType.RightTopOrientation,
OrientationType.RightBottomOrientation,
OrientationType.LeftBottomOrientation,
]
def _colorspace(self, image, colorspace):
if colorspace == 'RGB':
image.type(ImageType.TrueColorMatteType)
elif colorspace == 'GRAY':
image.type(ImageType.GrayscaleMatteType)
else:
return image
return image
def _scale(self, image, width, height):
geometry = Geometry(width, height)
image.scale(geometry)
return image
def _crop(self, image, width, height, x_offset, y_offset):
geometry = Geometry(width, height, x_offset, y_offset)
image.crop(geometry)
return image
def _get_raw_data(self, image, format_, quality, image_info=None, progressive=False):
image.magick(format_.encode('utf8'))
image.quality(quality)
if format_ == 'JPEG' and progressive:
image.interlaceType(InterlaceType.LineInterlace)
blob = Blob()
image.write(blob)
return get_blob_data(blob)
sorl-thumbnail/sorl/thumbnail/templatetags/ 0000755 0001750 0001750 00000000000 13141176077 017615 5 ustar wmb wmb sorl-thumbnail/sorl/thumbnail/templatetags/thumbnail.py 0000644 0001750 0001750 00000020733 13141176077 022157 0 ustar wmb wmb # encoding=utf-8
from __future__ import unicode_literals
import decimal
import logging
import sys
import re
import os
from functools import wraps
from django.template import Library, Node, NodeList, TemplateSyntaxError
from django.utils.encoding import smart_str
from django.utils.six import text_type
from django.conf import settings
from sorl.thumbnail.conf import settings as sorl_settings
from sorl.thumbnail import default
from sorl.thumbnail.images import ImageFile, DummyImageFile
from sorl.thumbnail.parsers import parse_geometry
from sorl.thumbnail.shortcuts import get_thumbnail
register = Library()
kw_pat = re.compile(r'^(?P[\w]+)=(?P.+)$')
logger = logging.getLogger('sorl.thumbnail')
def safe_filter(error_output=''):
"""
A safe filter decorator only raising errors when ``THUMBNAIL_DEBUG`` is
``True`` otherwise returning ``error_output``.
"""
def inner(f):
@wraps(f)
def wrapper(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as err:
if sorl_settings.THUMBNAIL_DEBUG:
raise
logger.error('Thumbnail filter failed: %s' % err.message,
exc_info=sys.exc_info())
return error_output
return wrapper
return inner
class ThumbnailNodeBase(Node):
"""
A Node that renders safely
"""
nodelist_empty = NodeList()
def render(self, context):
try:
return self._render(context)
except Exception:
if sorl_settings.THUMBNAIL_DEBUG:
raise
error_message = 'Thumbnail tag failed'
if context.template.engine.debug:
try:
error_message_template = (
"Thumbnail tag failed "
"in template {template_name}, error at: "
"{tag_text}"
)
template_origin, (position_start, position_end) = self.source
template_text = template_origin.reload()
tag_text = template_text[position_start:position_end]
error_message = error_message_template.format(
template_name=template_origin.name,
tag_text=tag_text,
)
except Exception:
pass
logger.exception(error_message)
return self.nodelist_empty.render(context)
def _render(self, context):
raise NotImplementedError()
class ThumbnailNode(ThumbnailNodeBase):
child_nodelists = ('nodelist_file', 'nodelist_empty')
error_msg = ('Syntax error. Expected: ``thumbnail source geometry '
'[key1=val1 key2=val2...] as var``')
def __init__(self, parser, token):
bits = token.split_contents()
self.file_ = parser.compile_filter(bits[1])
self.geometry = parser.compile_filter(bits[2])
self.options = []
self.as_var = None
self.nodelist_file = None
if bits[-2] == 'as':
options_bits = bits[3:-2]
else:
options_bits = bits[3:]
for bit in options_bits:
m = kw_pat.match(bit)
if not m:
raise TemplateSyntaxError(self.error_msg)
key = smart_str(m.group('key'))
expr = parser.compile_filter(m.group('value'))
self.options.append((key, expr))
if bits[-2] == 'as':
self.as_var = bits[-1]
self.nodelist_file = parser.parse(('empty', 'endthumbnail',))
if parser.next_token().contents == 'empty':
self.nodelist_empty = parser.parse(('endthumbnail',))
parser.delete_first_token()
def _render(self, context):
file_ = self.file_.resolve(context)
geometry = self.geometry.resolve(context)
options = {}
for key, expr in self.options:
noresolve = {'True': True, 'False': False, 'None': None}
value = noresolve.get(text_type(expr), expr.resolve(context))
if key == 'options':
options.update(value)
else:
options[key] = value
thumbnail = get_thumbnail(file_, geometry, **options)
if not thumbnail or (isinstance(thumbnail, DummyImageFile) and self.nodelist_empty):
if self.nodelist_empty:
return self.nodelist_empty.render(context)
else:
return ''
if self.as_var:
context.push()
context[self.as_var] = thumbnail
output = self.nodelist_file.render(context)
context.pop()
else:
output = thumbnail.url
return output
def __repr__(self):
return ""
def __iter__(self):
for node in self.nodelist_file:
yield node
for node in self.nodelist_empty:
yield node
@register.filter
def resolution(file_, resolution_string):
"""
A filter to return the URL for the provided resolution of the thumbnail.
"""
if sorl_settings.THUMBNAIL_DUMMY:
dummy_source = sorl_settings.THUMBNAIL_DUMMY_SOURCE
source = dummy_source.replace('%(width)s', '(?P[0-9]+)')
source = source.replace('%(height)s', '(?P[0-9]+)')
source = re.compile(source)
try:
resolution = decimal.Decimal(resolution_string.strip('x'))
info = source.match(file_).groupdict()
info = {dimension: int(int(size) * resolution) for (dimension, size) in info.items()}
return dummy_source % info
except (AttributeError, TypeError, KeyError):
# If we can't manipulate the dummy we shouldn't change it at all
return file_
filename, extension = os.path.splitext(file_)
return '%s@%s%s' % (filename, resolution_string, extension)
@register.tag
def thumbnail(parser, token):
return ThumbnailNode(parser, token)
@safe_filter(error_output=False)
@register.filter
def is_portrait(file_):
"""
A very handy filter to determine if an image is portrait or landscape.
"""
if sorl_settings.THUMBNAIL_DUMMY:
return sorl_settings.THUMBNAIL_DUMMY_RATIO < 1
if not file_:
return False
image_file = default.kvstore.get_or_set(ImageFile(file_))
return image_file.is_portrait()
@safe_filter(error_output='auto')
@register.filter
def margin(file_, geometry_string):
"""
Returns the calculated margin for an image and geometry
"""
if not file_ or (sorl_settings.THUMBNAIL_DUMMY or isinstance(file_, DummyImageFile)):
return 'auto'
margin = [0, 0, 0, 0]
image_file = default.kvstore.get_or_set(ImageFile(file_))
x, y = parse_geometry(geometry_string, image_file.ratio)
ex = x - image_file.x
margin[3] = ex / 2
margin[1] = ex / 2
if ex % 2:
margin[1] += 1
ey = y - image_file.y
margin[0] = ey / 2
margin[2] = ey / 2
if ey % 2:
margin[2] += 1
return ' '.join(['%dpx' % n for n in margin])
@safe_filter(error_output='auto')
@register.filter
def background_margin(file_, geometry_string):
"""
Returns the calculated margin for a background image and geometry
"""
if not file_ or sorl_settings.THUMBNAIL_DUMMY:
return 'auto'
margin = [0, 0]
image_file = default.kvstore.get_or_set(ImageFile(file_))
x, y = parse_geometry(geometry_string, image_file.ratio)
ex = x - image_file.x
margin[0] = ex / 2
ey = y - image_file.y
margin[1] = ey / 2
return ' '.join(['%spx' % n for n in margin])
def text_filter(regex_base, value):
"""
Helper method to regex replace images with captions in different markups
"""
regex = regex_base % {
're_cap': '[a-zA-Z0-9\.\,:;/_ \(\)\-\!\?\"]+',
're_img': '[a-zA-Z0-9\.:/_\-\% ]+'
}
images = re.findall(regex, value)
for i in images:
image = i[1]
if image.startswith(settings.MEDIA_URL):
image = image[len(settings.MEDIA_URL):]
im = get_thumbnail(image, str(sorl_settings.THUMBNAIL_FILTER_WIDTH))
value = value.replace(i[1], im.url)
return value
@safe_filter(error_output='auto')
@register.filter
def markdown_thumbnails(value):
return text_filter('!\[(%(re_cap)s)?\][ ]?\((%(re_img)s)\)', value)
@safe_filter(error_output='auto')
@register.filter
def html_thumbnails(value):
return text_filter('