added file previews

This commit is contained in:
Alexander Karpov 2023-01-23 00:45:16 +03:00
parent 222138193b
commit efd072ba62
3 changed files with 285 additions and 1 deletions

View File

@ -0,0 +1,80 @@
from math import ceil
from PIL import Image, ImageDraw, ImageFont
from preview_generator.manager import PreviewManager
cache_path = "/tmp/preview_cache"
PIL_GRAYSCALE = "L"
PIL_WIDTH_INDEX = 0
PIL_HEIGHT_INDEX = 1
COMMON_MONO_FONT_FILENAMES = [
"DejaVuSansMono.ttf",
"Consolas Mono.ttf",
"Consola.ttf",
]
def textfile_to_image(textfile_path) -> Image:
"""Convert text file to a grayscale image.
arguments:
textfile_path - the content of this file will be converted to an image
font_path - path to a font file (for example impact.ttf)
"""
# parse the file into lines stripped of whitespace on the right side
with open(textfile_path) as f:
lines = tuple(line.rstrip() for line in f.readlines())
font = None
large_font = 20 # get better resolution with larger size
for font_filename in COMMON_MONO_FONT_FILENAMES:
try:
font = ImageFont.truetype(font_filename, size=large_font)
print(f'Using font "{font_filename}".')
break
except OSError:
print(f'Could not load font "{font_filename}".')
if font is None:
font = ImageFont.load_default()
print("Using default font.")
def _font_points_to_pixels(pt):
return round(pt * 96.0 / 72)
margin_pixels = 20
# height of the background image
tallest_line = max(lines, key=lambda line: font.getsize(line)[PIL_HEIGHT_INDEX])
max_line_height = _font_points_to_pixels(
font.getsize(tallest_line)[PIL_HEIGHT_INDEX]
)
realistic_line_height = max_line_height * 0.8
image_height = int(ceil(realistic_line_height * len(lines) + 2 * margin_pixels))
widest_line = max(lines, key=lambda s: font.getsize(s)[PIL_WIDTH_INDEX])
max_line_width = _font_points_to_pixels(font.getsize(widest_line)[PIL_WIDTH_INDEX])
image_width = int(ceil(max_line_width + (2 * margin_pixels)))
# draw the background
background_color = 255 # white
image = Image.new(
PIL_GRAYSCALE, (image_width, image_height), color=background_color
)
draw = ImageDraw.Draw(image)
font_color = 0
horizontal_position = margin_pixels
for i, line in enumerate(lines):
vertical_position = int(round(margin_pixels + (i * realistic_line_height)))
draw.text(
(horizontal_position, vertical_position), line, fill=font_color, font=font
)
return image
def create_preview(file_path: str) -> str:
# TODO: add text image generation/code image
manager = PreviewManager(cache_path, create_folder=True)
path_to_preview_image = manager.get_jpeg_preview(file_path)
return path_to_preview_image

205
poetry.lock generated
View File

@ -289,6 +289,48 @@ d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "cairocffi"
version = "1.4.0"
description = "cffi-based cairo bindings for Python"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "cairocffi-1.4.0.tar.gz", hash = "sha256:509339b32ccd8d7b00c2204c32736cde78db53a32e6a162d312478d25626cd9a"},
]
[package.dependencies]
cffi = ">=1.1.0"
[package.extras]
doc = ["sphinx", "sphinx_rtd_theme"]
test = ["flake8", "isort", "numpy", "pikepdf", "pytest"]
xcb = ["xcffib (>=0.3.2)"]
[[package]]
name = "cairosvg"
version = "2.6.0"
description = "A Simple SVG Converter based on Cairo"
category = "main"
optional = false
python-versions = ">=3.5"
files = [
{file = "CairoSVG-2.6.0-py3-none-any.whl", hash = "sha256:05069d5316e9f02f33028942f96929e01782db41e6ff07d8454c8d021b5b52f3"},
{file = "CairoSVG-2.6.0.tar.gz", hash = "sha256:d5ec93e90101b3b6e82aa245d0546ee9b016cfda0b6344675159830d853d5d04"},
]
[package.dependencies]
cairocffi = "*"
cssselect2 = "*"
defusedxml = "*"
pillow = "*"
tinycss2 = "*"
[package.extras]
doc = ["sphinx", "sphinx-rtd-theme"]
test = ["flake8", "isort", "pytest"]
[[package]]
name = "celery"
version = "5.2.7"
@ -748,6 +790,26 @@ sdist = ["setuptools-rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
[[package]]
name = "cssselect2"
version = "0.7.0"
description = "CSS selectors for Python ElementTree"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "cssselect2-0.7.0-py3-none-any.whl", hash = "sha256:fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969"},
{file = "cssselect2-0.7.0.tar.gz", hash = "sha256:1ccd984dab89fc68955043aca4e1b03e0cf29cad9880f6e28e3ba7a74b14aa5a"},
]
[package.dependencies]
tinycss2 = "*"
webencodings = "*"
[package.extras]
doc = ["sphinx", "sphinx_rtd_theme"]
test = ["flake8", "isort", "pytest"]
[[package]]
name = "decorator"
version = "5.1.1"
@ -1485,6 +1547,24 @@ files = [
[package.dependencies]
python-dateutil = ">=2.4"
[[package]]
name = "ffmpeg-python"
version = "0.2.0"
description = "Python bindings for FFmpeg - with complex filtering support"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "ffmpeg-python-0.2.0.tar.gz", hash = "sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127"},
{file = "ffmpeg_python-0.2.0-py3-none-any.whl", hash = "sha256:ac441a0404e053f8b6a1113a77c0f452f1cfc62f6344a769475ffdc0f56c23c5"},
]
[package.dependencies]
future = "*"
[package.extras]
dev = ["Sphinx (==2.1.0)", "future (==0.17.1)", "numpy (==1.16.4)", "pytest (==4.6.1)", "pytest-mock (==1.10.4)", "tox (==3.12.1)"]
[[package]]
name = "filelock"
version = "3.9.0"
@ -1568,6 +1648,17 @@ files = [
{file = "funcy-1.17.tar.gz", hash = "sha256:40b9b9a88141ae6a174df1a95861f2b82f2fdc17669080788b73a3ed9370e968"},
]
[[package]]
name = "future"
version = "0.18.3"
description = "Clean single-source support for Python 3 and 2"
category = "main"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
{file = "future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"},
]
[[package]]
name = "gunicorn"
version = "20.1.0"
@ -2450,6 +2541,37 @@ nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
virtualenv = ">=20.10.0"
[[package]]
name = "preview-generator"
version = "0.29"
description = "A library for generating preview (thumbnails, text or json overview) for file-based content"
category = "main"
optional = false
python-versions = ">= 3.7"
files = [
{file = "preview_generator-0.29.tar.gz", hash = "sha256:343978dace795b4b9da862d96f2e4a9c088e58a255c32a97ecd8729d4947377a"},
]
[package.dependencies]
cairosvg = {version = "*", optional = true, markers = "extra == \"all\""}
ffmpeg-python = {version = "*", optional = true, markers = "extra == \"all\""}
filelock = "*"
pyexifinfo = "*"
python-magic = "*"
Wand = "*"
xvfbwrapper = {version = "*", optional = true, markers = "extra == \"all\""}
[package.extras]
3d = ["vtk"]
all = ["cairosvg", "ffmpeg-python", "xvfbwrapper", "xvfbwrapper"]
cairosvg = ["cairosvg"]
dev = ["ImageHash", "black", "flake8", "isort", "mypy", "pre-commit", "pytest", "pytest-dotenv"]
drawio = ["xvfbwrapper"]
raw = ["rawpy"]
scribus = ["xvfbwrapper"]
testing = ["ImageHash", "pytest", "pytest-dotenv"]
video = ["ffmpeg-python"]
[[package]]
name = "prometheus-client"
version = "0.15.0"
@ -2639,6 +2761,17 @@ files = [
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
]
[[package]]
name = "pyexifinfo"
version = "0.4.0"
description = "Simple Metadata extraction using Exiftool"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "pyexifinfo-0.4.0.tar.gz", hash = "sha256:578b34b3c593fe77bbe6b62588f9f2ec679dca63f7d486148c9a6ff1fdd4bdc9"},
]
[[package]]
name = "pyflakes"
version = "3.0.1"
@ -2892,6 +3025,18 @@ files = [
[package.dependencies]
six = ">=1.5"
[[package]]
name = "python-magic"
version = "0.4.27"
description = "File type identification using libmagic"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
{file = "python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b"},
{file = "python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"},
]
[[package]]
name = "python-slugify"
version = "7.0.0"
@ -3370,6 +3515,25 @@ files = [
{file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
]
[[package]]
name = "tinycss2"
version = "1.2.1"
description = "A tiny CSS parser"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"},
{file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"},
]
[package.dependencies]
webencodings = ">=0.4"
[package.extras]
doc = ["sphinx", "sphinx_rtd_theme"]
test = ["flake8", "isort", "pytest"]
[[package]]
name = "tomli"
version = "2.0.1"
@ -3568,6 +3732,22 @@ platformdirs = ">=2.4,<3"
docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"]
testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
[[package]]
name = "wand"
version = "0.6.11"
description = "Ctypes-based simple MagickWand API binding for Python"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "Wand-0.6.11-py2.py3-none-any.whl", hash = "sha256:1b77e25439ace57f665d1ccc6cff2766fad0834005b89ae3e7aaf3ba12b124b0"},
{file = "Wand-0.6.11.tar.gz", hash = "sha256:b661700da9f8f1e931e52726e4fc643a565b9514f5883d41b773e3c37c9fa995"},
]
[package.extras]
doc = ["Sphinx (>=5.3.0)"]
test = ["pytest (>=7.2.0)"]
[[package]]
name = "watchdog"
version = "2.2.1"
@ -3651,6 +3831,18 @@ files = [
{file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
]
[[package]]
name = "webencodings"
version = "0.5.1"
description = "Character encoding aliases for legacy web content"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
{file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
]
[[package]]
name = "werkzeug"
version = "2.2.2"
@ -3759,7 +3951,18 @@ files = [
{file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
]
[[package]]
name = "xvfbwrapper"
version = "0.2.9"
description = "run headless display inside X virtual framebuffer (Xvfb)"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "xvfbwrapper-0.2.9.tar.gz", hash = "sha256:bcf4ae571941b40254faf7a73432dfc119ad21ce688f1fdec533067037ecfc24"},
]
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "63c03ff6211f20d9ad19aa9761aac2896a70345a441315ea44e09e8e0a370fcc"
content-hash = "e9c2b607d25d574affa4ee7b3dd4c184dfbb389fae0f64dd15463a8bbb27e9f0"

View File

@ -68,6 +68,7 @@ django-sekizai = "^4.0.0"
amzqr = "^0.0.1"
django-chunked-upload = "^2.0.0"
drf-chunked-upload = "^0.5.1"
preview-generator = {extras = ["all"], version = "^0.29"}
[build-system]