|
|
from django.contrib.staticfiles import finders
|
|
|
from django.contrib.staticfiles.templatetags.staticfiles import static
|
|
|
from django.core.files.images import get_image_dimensions
|
|
|
from django.template.defaultfilters import filesizeformat
|
|
|
|
|
|
from boards import settings
|
|
|
from boards.abstracts.constants import THUMB_SIZES
|
|
|
from boards.settings import SECTION_EXTERNAL
|
|
|
from boards.utils import get_domain, cached_result, get_extension
|
|
|
|
|
|
URL_IMAGE_PATH = 'images/{}.png'
|
|
|
|
|
|
PROTOCOL_DELIMITER = ':'
|
|
|
DOMAIN_DELIMITER = '.'
|
|
|
|
|
|
FILE_STUB_IMAGE = 'images/file.png'
|
|
|
FILE_STUB_URL = 'url'
|
|
|
FILE_FILEFORMAT = 'images/fileformats/{}.png'
|
|
|
|
|
|
|
|
|
FILE_TYPES_VIDEO = (
|
|
|
'video/webm',
|
|
|
'video/mp4',
|
|
|
'video/mpeg',
|
|
|
'video/ogv',
|
|
|
)
|
|
|
FILE_TYPE_SVG = 'image/svg+xml'
|
|
|
FILE_TYPES_AUDIO = (
|
|
|
'audio/ogg',
|
|
|
'audio/mpeg',
|
|
|
'audio/opus',
|
|
|
'audio/x-flac',
|
|
|
'audio/mpeg',
|
|
|
)
|
|
|
FILE_TYPES_IMAGE = (
|
|
|
'image/jpeg',
|
|
|
'image/jpg',
|
|
|
'image/png',
|
|
|
'image/bmp',
|
|
|
'image/gif',
|
|
|
)
|
|
|
|
|
|
PLAIN_FILE_FORMATS = {
|
|
|
'zip': 'archive',
|
|
|
'tar': 'archive',
|
|
|
'gz': 'archive',
|
|
|
'mid' : 'midi',
|
|
|
}
|
|
|
|
|
|
URL_PROTOCOLS = {
|
|
|
'magnet': 'magnet',
|
|
|
}
|
|
|
|
|
|
CSS_CLASS_IMAGE = 'image'
|
|
|
|
|
|
ABSTRACT_VIEW = '<div class="image">'\
|
|
|
'{}'\
|
|
|
'<div class="image-metadata"><a href="{}" download >{}, {}</a>'\
|
|
|
' <a class="file-menu" href="#" data-type="{}" data-search-url="{}" data-filename="{}" data-id="{}">⋮ </a></div>'\
|
|
|
'</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="thumb" 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>'
|
|
|
|
|
|
|
|
|
def get_viewers():
|
|
|
return AbstractViewer.__subclasses__()
|
|
|
|
|
|
|
|
|
class AbstractViewer:
|
|
|
def __init__(self, file, file_type, id, url):
|
|
|
self.file = file
|
|
|
self.file_type = file_type
|
|
|
self.id = id
|
|
|
self.url = url
|
|
|
self.extension = get_extension(self.file.name).lower()
|
|
|
|
|
|
@staticmethod
|
|
|
def supports(file_type):
|
|
|
return True
|
|
|
|
|
|
def get_view(self):
|
|
|
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')
|
|
|
if search_host:
|
|
|
if search_host.endswith('/'):
|
|
|
search_host = search_host[:-1]
|
|
|
search_url = search_host + self.file.url
|
|
|
else:
|
|
|
search_url = ''
|
|
|
return search_url
|
|
|
|
|
|
def get_format_view(self):
|
|
|
image_name = PLAIN_FILE_FORMATS.get(self.extension, self.extension)
|
|
|
file_name = FILE_FILEFORMAT.format(image_name)
|
|
|
|
|
|
if self.file_exists(file_name):
|
|
|
image = file_name
|
|
|
else:
|
|
|
image = FILE_STUB_IMAGE
|
|
|
|
|
|
w, h = self.get_static_dimensions(image)
|
|
|
|
|
|
return ABSTRACT_FORMAT_VIEW.format(self.file.url, static(image), w, h)
|
|
|
|
|
|
@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
|
|
|
|
|
|
|
|
|
class VideoViewer(AbstractViewer):
|
|
|
@staticmethod
|
|
|
def supports(file_type):
|
|
|
return file_type in FILE_TYPES_VIDEO
|
|
|
|
|
|
def get_format_view(self):
|
|
|
return VIDEO_FORMAT_VIEW.format(self.file.url)
|
|
|
|
|
|
|
|
|
class AudioViewer(AbstractViewer):
|
|
|
@staticmethod
|
|
|
def supports(file_type):
|
|
|
return file_type in FILE_TYPES_AUDIO
|
|
|
|
|
|
def get_format_view(self):
|
|
|
return AUDIO_FORMAT_VIEW.format(self.file.url)
|
|
|
|
|
|
|
|
|
class SvgViewer(AbstractViewer):
|
|
|
@staticmethod
|
|
|
def supports(file_type):
|
|
|
return file_type == FILE_TYPE_SVG
|
|
|
|
|
|
def get_format_view(self):
|
|
|
return SVG_FORMAT_VIEW.format(self.file.url, self.file.url)
|
|
|
|
|
|
|
|
|
class ImageViewer(AbstractViewer):
|
|
|
@staticmethod
|
|
|
def supports(file_type):
|
|
|
return file_type in FILE_TYPES_IMAGE
|
|
|
|
|
|
def get_format_view(self):
|
|
|
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()
|
|
|
|
|
|
preview_path = self.file.path.replace('.', '.200x150.')
|
|
|
try:
|
|
|
pre_width, pre_height = get_image_dimensions(preview_path)
|
|
|
except Exception:
|
|
|
return super().get_format_view()
|
|
|
|
|
|
split = self.file.url.rsplit('.', 1)
|
|
|
w, h = THUMB_SIZES[0]
|
|
|
thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
|
|
|
|
|
|
return IMAGE_FORMAT_VIEW.format(
|
|
|
thumb_url,
|
|
|
self.id,
|
|
|
pre_width, pre_height, width, height,
|
|
|
full=self.file.url)
|
|
|
|
|
|
|
|
|
class UrlViewer(AbstractViewer):
|
|
|
@staticmethod
|
|
|
def supports(file_type):
|
|
|
return file_type is None
|
|
|
|
|
|
def get_view(self):
|
|
|
return URL_VIEW.format(self.get_format_view(), get_domain(self.url))
|
|
|
|
|
|
def get_format_view(self):
|
|
|
protocol = self.url.split(PROTOCOL_DELIMITER)[0]
|
|
|
|
|
|
domain = get_domain(self.url)
|
|
|
|
|
|
image_path = URL_IMAGE_PATH.format(self._get_image_name(protocol, domain))
|
|
|
image = static(image_path)
|
|
|
w, h = self.get_static_dimensions(image_path)
|
|
|
|
|
|
return URL_FORMAT_VIEW.format(self.url, image, w, h)
|
|
|
|
|
|
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(DOMAIN_DELIMITER)
|
|
|
while len(levels) > 1:
|
|
|
domain = DOMAIN_DELIMITER.join(levels)
|
|
|
|
|
|
filename = 'images/domains/{}.png'.format(domain)
|
|
|
if self.file_exists(filename):
|
|
|
return 'domains/' + domain
|
|
|
else:
|
|
|
del levels[0]
|
|
|
|
|
|
@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
|
|
|
|
|
|
|