viewers.py
243 lines
| 6.9 KiB
| text/x-python
|
PythonLexer
neko259
|
r1716 | from django.contrib.staticfiles import finders | ||
from django.contrib.staticfiles.templatetags.staticfiles import static | ||||
neko259
|
r1590 | from django.core.files.images import get_image_dimensions | ||
neko259
|
r1273 | from django.template.defaultfilters import filesizeformat | ||
neko259
|
r1716 | |||
neko259
|
r1986 | from boards import settings | ||
neko259
|
r2008 | from boards.abstracts.constants import THUMB_SIZES | ||
from boards.settings import SECTION_EXTERNAL | ||||
neko259
|
r1871 | from boards.utils import get_domain, cached_result, get_extension | ||
neko259
|
r1715 | |||
neko259
|
r2040 | URL_IMAGE_PATH = 'images/{}.png' | ||
neko259
|
r2008 | PROTOCOL_DELIMITER = ':' | ||
DOMAIN_DELIMITER = '.' | ||||
neko259
|
r1279 | FILE_STUB_IMAGE = 'images/file.png' | ||
neko259
|
r1677 | FILE_STUB_URL = 'url' | ||
neko259
|
r1779 | FILE_FILEFORMAT = 'images/fileformats/{}.png' | ||
neko259
|
r1279 | |||
neko259
|
r1727 | |||
neko259
|
r1307 | FILE_TYPES_VIDEO = ( | ||
neko259
|
r1866 | 'video/webm', | ||
'video/mp4', | ||||
'video/mpeg', | ||||
'video/ogv', | ||||
neko259
|
r1307 | ) | ||
neko259
|
r1908 | FILE_TYPE_SVG = 'image/svg+xml' | ||
neko259
|
r1279 | FILE_TYPES_AUDIO = ( | ||
neko259
|
r1866 | 'audio/ogg', | ||
neko259
|
r1870 | 'audio/mpeg', | ||
neko259
|
r1866 | 'audio/opus', | ||
neko259
|
r1867 | 'audio/x-flac', | ||
neko259
|
r1869 | 'audio/mpeg', | ||
neko259
|
r1279 | ) | ||
neko259
|
r1590 | FILE_TYPES_IMAGE = ( | ||
neko259
|
r1866 | 'image/jpeg', | ||
'image/jpg', | ||||
'image/png', | ||||
'image/bmp', | ||||
'image/gif', | ||||
neko259
|
r1590 | ) | ||
neko259
|
r1279 | |||
neko259
|
r1326 | PLAIN_FILE_FORMATS = { | ||
neko259
|
r1871 | 'zip': 'archive', | ||
'tar': 'archive', | ||||
neko259
|
r1713 | 'gz': 'archive', | ||
neko259
|
r1871 | 'mid' : 'midi', | ||
neko259
|
r1326 | } | ||
neko259
|
r1677 | URL_PROTOCOLS = { | ||
'magnet': 'magnet', | ||||
} | ||||
neko259
|
r1590 | CSS_CLASS_IMAGE = 'image' | ||
neko259
|
r1828 | ABSTRACT_VIEW = '<div class="image">'\ | ||
'{}'\ | ||||
neko259
|
r1937 | '<div class="image-metadata"><a href="{}" download >{}, {}</a>'\ | ||
neko259
|
r1944 | ' <a class="file-menu" href="#" data-type="{}" data-search-url="{}" data-filename="{}" data-id="{}">⋮ </a></div>'\ | ||
neko259
|
r1828 | '</div>' | ||
URL_VIEW = '<div class="image">' \ | ||||
'{}' \ | ||||
'<div class="image-metadata">{}</div>' \ | ||||
'</div>' | ||||
ABSTRACT_FORMAT_VIEW = '<a href="{}">'\ | ||||
'<img class="url-image" src="{}" width="{}" height="{}"/>'\ | ||||
'</a>' | ||||
VIDEO_FORMAT_VIEW = '<video width="200" height="150" controls src="{}"></video>' | ||||
AUDIO_FORMAT_VIEW = '<audio controls src="{}"></audio>' | ||||
neko259
|
r2040 | IMAGE_FORMAT_VIEW = '<a class="thumb" href="{full}">' \ | ||
neko259
|
r1828 | '<img class="post-image-preview"' \ | ||
' src="{}"' \ | ||||
' alt="{}"' \ | ||||
' width="{}"' \ | ||||
' height="{}"' \ | ||||
' data-width="{}"' \ | ||||
' data-height="{}" />' \ | ||||
'</a>' | ||||
SVG_FORMAT_VIEW = '<a class="thumb" href="{}">'\ | ||||
'<img class="post-image-preview" width="200" height="150" src="{}" />'\ | ||||
'</a>' | ||||
URL_FORMAT_VIEW = '<a href="{}">' \ | ||||
neko259
|
r2090 | '<img class="url-image" src="{}" width="{}" height="{}" title="{}" />' \ | ||
neko259
|
r1828 | '</a>' | ||
neko259
|
r1273 | |||
neko259
|
r1286 | def get_viewers(): | ||
return AbstractViewer.__subclasses__() | ||||
neko259
|
r1273 | class AbstractViewer: | ||
neko259
|
r1940 | def __init__(self, file, file_type, id, url): | ||
neko259
|
r1273 | self.file = file | ||
self.file_type = file_type | ||||
neko259
|
r1940 | self.id = id | ||
neko259
|
r1660 | self.url = url | ||
neko259
|
r1967 | self.extension = get_extension(self.file.name).lower() | ||
neko259
|
r1273 | |||
@staticmethod | ||||
def supports(file_type): | ||||
neko259
|
r1279 | return True | ||
neko259
|
r1273 | |||
def get_view(self): | ||||
neko259
|
r2008 | return ABSTRACT_VIEW.format(self.get_format_view(), self.file.url, | ||
self.file_type, filesizeformat(self.file.size), | ||||
self.file_type, self._get_search_url(), self.file.name, self.id) | ||||
def _get_search_url(self): | ||||
search_host = settings.get(SECTION_EXTERNAL, 'ImageSearchHost') | ||||
neko259
|
r1817 | if search_host: | ||
neko259
|
r1821 | if search_host.endswith('/'): | ||
search_host = search_host[:-1] | ||||
neko259
|
r1817 | search_url = search_host + self.file.url | ||
else: | ||||
search_url = '' | ||||
neko259
|
r2008 | return search_url | ||
neko259
|
r1300 | |||
def get_format_view(self): | ||||
neko259
|
r1871 | image_name = PLAIN_FILE_FORMATS.get(self.extension, self.extension) | ||
neko259
|
r1779 | file_name = FILE_FILEFORMAT.format(image_name) | ||
neko259
|
r2014 | if self.file_exists(file_name): | ||
neko259
|
r1779 | image = file_name | ||
neko259
|
r1326 | else: | ||
image = FILE_STUB_IMAGE | ||||
neko259
|
r2014 | w, h = self.get_static_dimensions(image) | ||
neko259
|
r1684 | |||
neko259
|
r1828 | return ABSTRACT_FORMAT_VIEW.format(self.file.url, static(image), w, h) | ||
neko259
|
r1273 | |||
neko259
|
r2014 | @cached_result() | ||
def get_static_dimensions(self, filename): | ||||
file_path = finders.find(filename) | ||||
return get_image_dimensions(file_path) | ||||
@cached_result() | ||||
def file_exists(self, filename): | ||||
return finders.find(filename) is not None | ||||
neko259
|
r1273 | |||
neko259
|
r1307 | class VideoViewer(AbstractViewer): | ||
neko259
|
r1273 | @staticmethod | ||
def supports(file_type): | ||||
neko259
|
r1307 | return file_type in FILE_TYPES_VIDEO | ||
neko259
|
r1273 | |||
neko259
|
r1300 | def get_format_view(self): | ||
neko259
|
r1828 | return VIDEO_FORMAT_VIEW.format(self.file.url) | ||
neko259
|
r1273 | |||
neko259
|
r1279 | |||
class AudioViewer(AbstractViewer): | ||||
@staticmethod | ||||
def supports(file_type): | ||||
return file_type in FILE_TYPES_AUDIO | ||||
neko259
|
r1300 | def get_format_view(self): | ||
neko259
|
r1828 | return AUDIO_FORMAT_VIEW.format(self.file.url) | ||
neko259
|
r1284 | |||
class SvgViewer(AbstractViewer): | ||||
@staticmethod | ||||
def supports(file_type): | ||||
return file_type == FILE_TYPE_SVG | ||||
neko259
|
r1300 | def get_format_view(self): | ||
neko259
|
r1828 | return SVG_FORMAT_VIEW.format(self.file.url, self.file.url) | ||
neko259
|
r1590 | |||
class ImageViewer(AbstractViewer): | ||||
@staticmethod | ||||
def supports(file_type): | ||||
return file_type in FILE_TYPES_IMAGE | ||||
def get_format_view(self): | ||||
neko259
|
r1820 | try: | ||
width, height = get_image_dimensions(self.file.path) | ||||
except Exception: | ||||
# If the image is a decompression bomb, treat it as just a regular | ||||
# file | ||||
return super().get_format_view() | ||||
neko259
|
r1590 | preview_path = self.file.path.replace('.', '.200x150.') | ||
neko259
|
r1906 | try: | ||
pre_width, pre_height = get_image_dimensions(preview_path) | ||||
except Exception: | ||||
return super().get_format_view() | ||||
neko259
|
r1590 | |||
split = self.file.url.rsplit('.', 1) | ||||
neko259
|
r2008 | w, h = THUMB_SIZES[0] | ||
neko259
|
r1590 | thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1]) | ||
neko259
|
r2040 | return IMAGE_FORMAT_VIEW.format( | ||
thumb_url, | ||||
self.id, | ||||
pre_width, pre_height, width, height, | ||||
full=self.file.url) | ||||
neko259
|
r1590 | |||
neko259
|
r1660 | |||
class UrlViewer(AbstractViewer): | ||||
@staticmethod | ||||
def supports(file_type): | ||||
return file_type is None | ||||
def get_view(self): | ||||
neko259
|
r1828 | return URL_VIEW.format(self.get_format_view(), get_domain(self.url)) | ||
neko259
|
r1677 | |||
neko259
|
r1660 | def get_format_view(self): | ||
neko259
|
r2008 | protocol = self.url.split(PROTOCOL_DELIMITER)[0] | ||
neko259
|
r1765 | |||
domain = get_domain(self.url) | ||||
neko259
|
r1695 | |||
neko259
|
r2040 | image_path = URL_IMAGE_PATH.format(self._get_image_name(protocol, domain)) | ||
neko259
|
r1718 | image = static(image_path) | ||
neko259
|
r2014 | w, h = self.get_static_dimensions(image_path) | ||
neko259
|
r1684 | |||
neko259
|
r2090 | return URL_FORMAT_VIEW.format(self.url, image, w, h, self.url) | ||
neko259
|
r1765 | |||
neko259
|
r1772 | def _find_image_for_domains(self, domain): | ||
""" | ||||
Searches for the domain image for every domain level except top. | ||||
E.g. for l3.example.co.uk it will search for l3.example.co.uk, then | ||||
example.co.uk, then co.uk | ||||
""" | ||||
neko259
|
r2008 | levels = domain.split(DOMAIN_DELIMITER) | ||
neko259
|
r1772 | while len(levels) > 1: | ||
neko259
|
r2008 | domain = DOMAIN_DELIMITER.join(levels) | ||
neko259
|
r1765 | |||
neko259
|
r1772 | filename = 'images/domains/{}.png'.format(domain) | ||
neko259
|
r2014 | if self.file_exists(filename): | ||
neko259
|
r1772 | return 'domains/' + domain | ||
else: | ||||
del levels[0] | ||||
neko259
|
r2015 | |||
@cached_result() | ||||
def _get_image_name(self, protocol, domain): | ||||
if protocol in URL_PROTOCOLS: | ||||
url_image_name = URL_PROTOCOLS.get(protocol) | ||||
elif domain: | ||||
url_image_name = self._find_image_for_domains(domain) or FILE_STUB_URL | ||||
else: | ||||
url_image_name = FILE_STUB_URL | ||||
return url_image_name | ||||