viewers.py
237 lines
| 6.7 KiB
| text/x-python
|
PythonLexer
|
r1716 | from django.contrib.staticfiles import finders | ||
from django.contrib.staticfiles.templatetags.staticfiles import static | ||||
|
r1590 | from django.core.files.images import get_image_dimensions | ||
|
r1273 | from django.template.defaultfilters import filesizeformat | ||
|
r1716 | |||
|
r1986 | from boards import settings | ||
|
r1871 | from boards.utils import get_domain, cached_result, get_extension | ||
|
r1715 | |||
|
r1279 | FILE_STUB_IMAGE = 'images/file.png' | ||
|
r1677 | FILE_STUB_URL = 'url' | ||
|
r1779 | FILE_FILEFORMAT = 'images/fileformats/{}.png' | ||
|
r1279 | |||
|
r1727 | |||
|
r1307 | FILE_TYPES_VIDEO = ( | ||
|
r1866 | 'video/webm', | ||
'video/mp4', | ||||
'video/mpeg', | ||||
'video/ogv', | ||||
|
r1307 | ) | ||
|
r1908 | FILE_TYPE_SVG = 'image/svg+xml' | ||
|
r1279 | FILE_TYPES_AUDIO = ( | ||
|
r1866 | 'audio/ogg', | ||
|
r1870 | 'audio/mpeg', | ||
|
r1866 | 'audio/opus', | ||
|
r1867 | 'audio/x-flac', | ||
|
r1869 | 'audio/mpeg', | ||
|
r1279 | ) | ||
|
r1590 | FILE_TYPES_IMAGE = ( | ||
|
r1866 | 'image/jpeg', | ||
'image/jpg', | ||||
'image/png', | ||||
'image/bmp', | ||||
'image/gif', | ||||
|
r1590 | ) | ||
|
r1279 | |||
|
r1326 | PLAIN_FILE_FORMATS = { | ||
|
r1871 | 'zip': 'archive', | ||
'tar': 'archive', | ||||
|
r1713 | 'gz': 'archive', | ||
|
r1871 | 'mid' : 'midi', | ||
|
r1326 | } | ||
|
r1677 | URL_PROTOCOLS = { | ||
'magnet': 'magnet', | ||||
} | ||||
|
r1590 | CSS_CLASS_IMAGE = 'image' | ||
CSS_CLASS_THUMB = 'thumb' | ||||
|
r1828 | ABSTRACT_VIEW = '<div class="image">'\ | ||
'{}'\ | ||||
|
r1937 | '<div class="image-metadata"><a href="{}" download >{}, {}</a>'\ | ||
|
r1944 | ' <a class="file-menu" href="#" data-type="{}" data-search-url="{}" data-filename="{}" data-id="{}">⋮ </a></div>'\ | ||
|
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>' | ||||
IMAGE_FORMAT_VIEW = '<a class="{}" href="{full}">' \ | ||||
'<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="{}">' \ | ||||
'<img class="url-image" src="{}" width="{}" height="{}"/>' \ | ||||
'</a>' | ||||
|
r1273 | |||
|
r1286 | def get_viewers(): | ||
return AbstractViewer.__subclasses__() | ||||
|
r1684 | def get_static_dimensions(filename): | ||
file_path = finders.find(filename) | ||||
return get_image_dimensions(file_path) | ||||
|
r1718 | # TODO Move this to utils | ||
def file_exists(filename): | ||||
return finders.find(filename) is not None | ||||
|
r1273 | class AbstractViewer: | ||
|
r1940 | def __init__(self, file, file_type, id, url): | ||
|
r1273 | self.file = file | ||
self.file_type = file_type | ||||
|
r1940 | self.id = id | ||
|
r1660 | self.url = url | ||
|
r1967 | self.extension = get_extension(self.file.name).lower() | ||
|
r1273 | |||
@staticmethod | ||||
def supports(file_type): | ||||
|
r1279 | return True | ||
|
r1273 | |||
def get_view(self): | ||||
|
r1817 | search_host = settings.get('External', 'ImageSearchHost') | ||
if search_host: | ||||
|
r1821 | if search_host.endswith('/'): | ||
search_host = search_host[:-1] | ||||
|
r1817 | search_url = search_host + self.file.url | ||
else: | ||||
search_url = '' | ||||
|
r1937 | return ABSTRACT_VIEW.format(self.get_format_view(), self.file.url, | ||
|
r1828 | self.file_type, filesizeformat(self.file.size), | ||
|
r1940 | self.file_type, search_url, self.file.name, self.id) | ||
|
r1300 | |||
def get_format_view(self): | ||||
|
r1871 | image_name = PLAIN_FILE_FORMATS.get(self.extension, self.extension) | ||
|
r1779 | file_name = FILE_FILEFORMAT.format(image_name) | ||
if file_exists(file_name): | ||||
image = file_name | ||||
|
r1326 | else: | ||
image = FILE_STUB_IMAGE | ||||
|
r1684 | w, h = get_static_dimensions(image) | ||
|
r1828 | return ABSTRACT_FORMAT_VIEW.format(self.file.url, static(image), w, h) | ||
|
r1273 | |||
|
r1307 | class VideoViewer(AbstractViewer): | ||
|
r1273 | @staticmethod | ||
def supports(file_type): | ||||
|
r1307 | return file_type in FILE_TYPES_VIDEO | ||
|
r1273 | |||
|
r1300 | def get_format_view(self): | ||
|
r1828 | return VIDEO_FORMAT_VIEW.format(self.file.url) | ||
|
r1273 | |||
|
r1279 | |||
class AudioViewer(AbstractViewer): | ||||
@staticmethod | ||||
def supports(file_type): | ||||
return file_type in FILE_TYPES_AUDIO | ||||
|
r1300 | def get_format_view(self): | ||
|
r1828 | return AUDIO_FORMAT_VIEW.format(self.file.url) | ||
|
r1284 | |||
class SvgViewer(AbstractViewer): | ||||
@staticmethod | ||||
def supports(file_type): | ||||
return file_type == FILE_TYPE_SVG | ||||
|
r1300 | def get_format_view(self): | ||
|
r1828 | return SVG_FORMAT_VIEW.format(self.file.url, self.file.url) | ||
|
r1590 | |||
class ImageViewer(AbstractViewer): | ||||
@staticmethod | ||||
def supports(file_type): | ||||
return file_type in FILE_TYPES_IMAGE | ||||
def get_format_view(self): | ||||
metadata = '{}, {}'.format(self.file.name.split('.')[-1], | ||||
filesizeformat(self.file.size)) | ||||
|
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() | ||||
|
r1590 | preview_path = self.file.path.replace('.', '.200x150.') | ||
|
r1906 | try: | ||
pre_width, pre_height = get_image_dimensions(preview_path) | ||||
except Exception: | ||||
return super().get_format_view() | ||||
|
r1590 | |||
split = self.file.url.rsplit('.', 1) | ||||
w, h = 200, 150 | ||||
thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1]) | ||||
|
r1828 | return IMAGE_FORMAT_VIEW.format(CSS_CLASS_THUMB, | ||
thumb_url, | ||||
|
r1940 | self.id, | ||
|
r1828 | str(pre_width), | ||
str(pre_height), str(width), str(height), | ||||
full=self.file.url, image_meta=metadata) | ||||
|
r1590 | |||
|
r1660 | |||
class UrlViewer(AbstractViewer): | ||||
@staticmethod | ||||
def supports(file_type): | ||||
return file_type is None | ||||
def get_view(self): | ||||
|
r1828 | return URL_VIEW.format(self.get_format_view(), get_domain(self.url)) | ||
|
r1677 | |||
|
r1660 | def get_format_view(self): | ||
|
r1776 | protocol = self.url.split(':')[0] | ||
|
r1765 | |||
domain = get_domain(self.url) | ||||
|
r1695 | |||
if protocol in URL_PROTOCOLS: | ||||
url_image_name = URL_PROTOCOLS.get(protocol) | ||||
|
r1765 | elif domain: | ||
|
r1772 | url_image_name = self._find_image_for_domains(domain) or FILE_STUB_URL | ||
|
r1765 | else: | ||
url_image_name = FILE_STUB_URL | ||||
|
r1695 | |||
|
r1718 | image_path = 'images/{}.png'.format(url_image_name) | ||
image = static(image_path) | ||||
w, h = get_static_dimensions(image_path) | ||||
|
r1684 | |||
|
r1828 | return URL_FORMAT_VIEW.format(self.url, image, w, h) | ||
|
r1765 | |||
|
r1772 | @cached_result() | ||
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 | ||||
""" | ||||
levels = domain.split('.') | ||||
while len(levels) > 1: | ||||
domain = '.'.join(levels) | ||||
|
r1765 | |||
|
r1772 | filename = 'images/domains/{}.png'.format(domain) | ||
if file_exists(filename): | ||||
return 'domains/' + domain | ||||
else: | ||||
del levels[0] | ||||
|
r1765 | |||