Generate thumbnails on the fly with PIL

Removed the need of having 3 copies of an image
Fixes more Sass(y) stuff
This commit is contained in:
Michał Gdula 2023-01-13 18:29:07 +00:00
parent 2455d3f88c
commit a10a5a8793
10 changed files with 149 additions and 76 deletions

View file

@ -5,7 +5,7 @@ print("""
| |_| | | | | | |_| | |__| __/ (_| \\__ \\ | |_| | | | | | |_| | |__| __/ (_| \\__ \\
\\___/|_| |_|_|\\__, |_____\\___|\\__, |___/ \\___/|_| |_|_|\\__, |_____\\___|\\__, |___/
|___/ |___/ |___/ |___/
Created by Fluffy Bean - Version 110123 Created by Fluffy Bean - Version 130123
""") """)
# Import base packages # Import base packages

View file

@ -1,95 +1,97 @@
from flask import Blueprint, render_template, current_app, send_from_directory, request, g, abort, flash from flask import Blueprint, render_template, current_app, send_from_directory, send_file, request, g, abort, flash
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from gallery.auth import login_required from gallery.auth import login_required
from gallery.db import get_db from gallery.db import get_db
from PIL import Image from PIL import Image, ImageOps
import io
import os import os
from uuid import uuid4 from uuid import uuid4
blueprint = Blueprint('viewsbp', __name__, url_prefix='/api') blueprint = Blueprint('viewsbp', __name__, url_prefix='/api')
@blueprint.route('/uploads/<quality>/<file>') @blueprint.route('/uploads/<file>/<int:quality>', methods=['GET'])
def uploads(quality, file): def uploads(file, quality):
dir = os.path.join(current_app.config['UPLOAD_FOLDER'], secure_filename(quality)) # If quality is 0, return original file
file = secure_filename(file) if quality == 0:
return send_from_directory(current_app.config['UPLOAD_FOLDER'], secure_filename(file), as_attachment=True)
return send_from_directory(dir, file, as_attachment=True) # Set variables
set_ext = {'jpg': 'jpeg', 'jpeg': 'jpeg', 'png': 'png', 'webp': 'webp'}
buff = io.BytesIO()
# Open image and set extension
img = Image.open(os.path.join(current_app.config['UPLOAD_FOLDER'], secure_filename(file)))
img_ext = os.path.splitext(secure_filename(file))[-1].lower().replace('.', '')
img_ext = set_ext[img_ext]
# Resize image and orientate correctly
img.thumbnail((quality, quality), Image.LANCZOS)
img = ImageOps.exif_transpose(img)
img.save(buff, img_ext)
# Seek to beginning of buffer and return
buff.seek(0)
return send_file(buff, mimetype='image/'+img_ext)
@blueprint.route('/upload', methods=['POST']) @blueprint.route('/upload', methods=['POST'])
@login_required @login_required
def upload(): def upload():
file = request.files['file'] form_file = request.files['file']
form = request.form form = request.form
# Check if file has been submitted if not form_file:
if not file:
flash('No selected file')
return abort(404) return abort(404)
# New file name and check if file extension is allowed img_ext = os.path.splitext(secure_filename(form_file.filename))[-1].lower()
file_ext = os.path.splitext(file.filename)[1].lower() img_name = f"GWAGWA_{uuid4().__str__()}{img_ext}"
file_name = f"GWAGWA_{uuid4().__str__()}{file_ext}"
if not file_ext in current_app.config['ALLOWED_EXTENSIONS']: if not img_ext in current_app.config['ALLOWED_EXTENSIONS']:
return 'File extension not allowed: '+file_ext return 'File extension not allowed: '+img_ext
# Save to database
try: try:
file.save(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER']+'/original', file_name)) db = get_db()
except: db.execute(
return 'Could not save file' 'INSERT INTO posts (file_name, author_id, description, alt)'
' VALUES (?, ?, ?, ?)',
# Resize image (img_name, g.user['id'], form['description'], form['alt'])
thumbnail_size = 300, 300 )
preview_size = 1000, 1000 db.commit()
img_file = Image.open(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER']+'/original', file_name))
try:
# save thumbnail
img_file.thumbnail(thumbnail_size, Image.Resampling.LANCZOS)
img_file.save(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER']+'/thumbnail', file_name))
# save preview
img_file.thumbnail(preview_size, Image.Resampling.LANCZOS)
img_file.save(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER']+'/preview', file_name))
except Exception as e: except Exception as e:
return 'Could not resize image: '+ str(e) abort(500)
db = get_db() # Save file
db.execute( try:
'INSERT INTO posts (file_name, author_id, description, alt)' form_file.save(os.path.join(current_app.config['UPLOAD_FOLDER'], img_name))
' VALUES (?, ?, ?, ?)', except:
(file_name, g.user['id'], form['description'], form['alt']) abort(500)
)
db.commit()
return 'Gwa Gwa' return 'Gwa Gwa'
@blueprint.route('/remove/<int:id>', methods=['POST']) @blueprint.route('/remove/<int:id>', methods=['POST'])
@login_required @login_required
def remove(id): def remove(id):
image = get_db().execute( img = get_db().execute(
'SELECT author_id, file_name FROM posts WHERE id = ?', 'SELECT author_id, file_name FROM posts WHERE id = ?',
(id,) (id,)
).fetchone() ).fetchone()
if image is None: if img is None:
abort(404) abort(404)
if image['author_id'] != g.user['id']: if img['author_id'] != g.user['id']:
abort(403) abort(403)
try: try:
os.remove(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER'], 'original', image['file_name'])) os.remove(os.path.join(current_app.config['UPLOAD_FOLDER'], img['file_name']))
os.remove(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER'], 'thumbnail', image['file_name']))
os.remove(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER'], 'preview', image['file_name']))
except Exception as e: except Exception as e:
return 'file error: '+str(e) abort(500)
try: try:
db = get_db() db = get_db()
db.execute('DELETE FROM posts WHERE id = ?', (id,)) db.execute('DELETE FROM posts WHERE id = ?', (id,))
db.commit() db.commit()
except: except:
return 'database error' abort(500)
return 'Gwa Gwa' return 'Gwa Gwa'

View file

@ -25,7 +25,7 @@ def image(id):
# Get exif data from image # Get exif data from image
try: try:
file = Image.open(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER'], 'original', image['file_name'])) file = Image.open(os.path.join(current_app.config['UPLOAD_FOLDER'], 'original', image['file_name']))
raw_exif = file.getexif() raw_exif = file.getexif()
human_exif = {} human_exif = {}
@ -43,6 +43,7 @@ def image(id):
except: except:
# Cringe, no file present # Cringe, no file present
human_exif = False human_exif = False
file = False
# All in le head # All in le head
return render_template('image.html', image=image, exif=human_exif, file=file) return render_template('image.html', image=image, exif=human_exif, file=file)

View file

@ -1,13 +1,13 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% block header %} {% block header %}
<img src="/api/uploads/original/{{ image['file_name'] }}" alt="leaves" onload="imgFade(this)" style="display: none;"/> <img src="/api/uploads/{{ image['file_name'] }}/1000" alt="leaves" onload="imgFade(this)" style="display: none;"/>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="image__fullscreen"> <div class="image__fullscreen">
<img <img
src="/api/uploads/original/{{ image['file_name'] }}" src="/api/uploads/{{ image['file_name'] }}/0"
onload="imgFade(this)" style="display:none;" onload="imgFade(this)" style="display:none;"
onerror="this.style.display='none'" onerror="this.style.display='none'"
/> />
@ -17,7 +17,7 @@
<div class="image__container"> <div class="image__container">
<img <img
class="image__item" class="image__item"
src="/api/uploads/original/{{ image['file_name'] }}" src="/api/uploads/{{ image['file_name'] }}/1000"
onload="imgFade(this)" style="display:none;" onload="imgFade(this)" style="display:none;"
onerror="this.src='/static/images/error.png'" onerror="this.src='/static/images/error.png'"
/> />
@ -62,26 +62,62 @@
</button> </button>
</div> </div>
</div> </div>
{% if image['alt'] != '' %}
<div class="image__info">
<div class="image__info-header">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2.5 24 24" fill="currentColor">
<path d="M3.656 17.979A1 1 0 0 1 2 17.243V15a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H8.003l-4.347 2.979zm.844-3.093a.536.536 0 0 0 .26-.069l2.355-1.638A1 1 0 0 1 7.686 13H12a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v5c0 .54.429.982 1 1 .41.016.707.083.844.226.128.134.135.36.156.79.003.063.003.177 0 .37a.5.5 0 0 0 .5.5z"></path><path d="M16 10.017a7.136 7.136 0 0 0 0 .369v-.37c.02-.43.028-.656.156-.79.137-.143.434-.21.844-.226.571-.018 1-.46 1-1V3a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1H5V2a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2v2.243a1 1 0 0 1-1.656.736L16 13.743v-3.726z"></path>
</svg>
<h2>Alt</h2>
</div>
<div class="image__info-content">
<p>{{ image['alt'] }}</p>
</div>
</div>
{% endif %}
{% if image['description'] != '' %} {% if image['description'] != '' %}
<div class="image__info"> <div class="image__info">
<h2>Description</h2> <div class="image__info-header">
<p>{{ image['description'] }}</p> <svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -2 24 24" fill="currentColor">
<path d="M3 0h10a3 3 0 0 1 3 3v14a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3zm0 2a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H3zm2 1h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2zm0 12h2a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zm0-4h6a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2zm0-4h6a1 1 0 0 1 0 2H5a1 1 0 1 1 0-2z"></path>
</svg>
<h2>Description</h2>
</div>
<div class="image__info-content">
<p>{{ image['description'] }}</p>
</div>
</div> </div>
{% endif %} {% endif %}
<div class="image__info"> <div class="image__info">
<h2>Info</h2> <div class="image__info-header">
<p>Filename: {{ image['file_name'] }}</p> <svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">
<p>Image ID: {{ image['id'] }}</p> <path d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-10a1 1 0 0 1 1 1v5a1 1 0 0 1-2 0V9a1 1 0 0 1 1-1zm0-1a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"></path>
<p>Author: {{ image['author_id'] }}</p> </svg>
<p>Upload date: {{ image['created_at'] }}</p> <h2>Info</h2>
<p>Dimensions: {{ file['width'] }}x{{ file['height'] }}</p> </div>
<div class="image__info-content">
<p>Filename: {{ image['file_name'] }}</p>
<p>Image ID: {{ image['id'] }}</p>
<p>Author: {{ image['author_id'] }}</p>
<p>Upload date: {{ image['created_at'] }}</p>
{% if file is not false %}
<p>Dimensions: {{ file['width'] }}x{{ file['height'] }}</p>
{% endif %}
</div>
</div> </div>
{% if exif is not false %} {% if exif is not false %}
<div class="image__info"> <div class="image__info">
<h2>Exif</h2> <div class="image__info-header">
{% for tag in exif %} <svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">
<p>{{ tag }}: {{ exif[tag] }}</p> <path d="M14.95 7.879l-.707-.707a1 1 0 0 1 1.414-1.415l.707.707 1.414-1.414-2.828-2.828L2.222 14.95l2.828 2.828 1.414-1.414L5.05 14.95a1 1 0 0 1 1.414-1.414L7.88 14.95l1.414-1.414-.707-.708A1 1 0 0 1 10 11.414l.707.707 1.414-1.414-1.414-1.414a1 1 0 0 1 1.414-1.414l1.415 1.414 1.414-1.414zM.808 13.536L13.536.808a2 2 0 0 1 2.828 0l2.828 2.828a2 2 0 0 1 0 2.828L6.464 19.192a2 2 0 0 1-2.828 0L.808 16.364a2 2 0 0 1 0-2.828z"></path>
{% endfor %} </svg>
<h2>Metadata</h2>
</div>
<div class="image__info-content">
{% for tag in exif %}
<p>{{ tag }}: {{ exif[tag] }}</p>
{% endfor %}
</div>
</div> </div>
{% endif %} {% endif %}
</div> </div>

View file

@ -15,7 +15,7 @@
<h2>{{ image['file_name'] }}</h2> <h2>{{ image['file_name'] }}</h2>
</div> </div>
<span class="gallery__item-filter"></span> <span class="gallery__item-filter"></span>
<img class="gallery__item-image" src="/api/uploads/original/{{ image['file_name'] }}" onload="imgFade(this)" style="display:none;"> <img class="gallery__item-image" src="/api/uploads/{{ image['file_name'] }}/400" onload="imgFade(this)" style="display:none;">
</a> </a>
{% endfor %} {% endfor %}
</div> </div>

View file

@ -9,6 +9,7 @@
</script> </script>
</head> </head>
<body> <body>
<div class="notification-list"></div>
<nav id="navRoot"> <nav id="navRoot">
<div> <div>
<a href="{{url_for('gallery.index')}}"> <a href="{{url_for('gallery.index')}}">
@ -66,7 +67,6 @@
document.onscroll = function() { document.onscroll = function() {
document.querySelector('header').style.opacity = `${1 - window.scrollY / 621}`; document.querySelector('header').style.opacity = `${1 - window.scrollY / 621}`;
//document.querySelector('header').style.height = `calc(50vh - ${window.scrollY / 2}px)`;
document.querySelector('header').style.top = `-${window.scrollY / 5}px`; document.querySelector('header').style.top = `-${window.scrollY / 5}px`;
if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) { if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) {

View file

@ -322,19 +322,43 @@
.image__info { .image__info {
margin: 0; margin: 0;
padding: 0.5rem; padding: 0;
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem;
background-color: $black200; background-color: $black200;
border-radius: $rad; border-radius: $rad;
border-left: $rad solid $green; //border-left: $rad solid $green;
box-sizing: border-box; box-sizing: border-box;
}
.image__info-header {
margin: 0;
padding: 0.5rem;
width: 100%;
height: 2rem;
display: flex;
justify-content: start;
align-items: center;
gap: 0.5rem;
background-color: $black300;
border-radius: $rad $rad 0 0;
svg {
margin: 0;
padding: 0;
width: 1.25rem;
height: 1.25rem;
fill: $green;
}
h2 { h2 {
margin: 0; margin: 0;
@ -350,6 +374,14 @@
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
} }
}
.image__info-content {
margin: 0;
padding: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
p { p {
margin: 0; margin: 0;

View file

@ -26,6 +26,8 @@ main {
box-sizing: border-box; box-sizing: border-box;
user-select: none;
img { img {
position: absolute; position: absolute;
top: 0; top: 0;

View file

@ -1,7 +1,7 @@
$black100: #151515; $black100: #1a1a1a;
$black200: #121212; $black200: #151515;
$black300: #101010; $black300: #101010;
$black400: #0e0e0e; $black400: #0b0b0b;
$white100: #e8e3e3; $white100: #e8e3e3;

View file

@ -2,7 +2,7 @@ from setuptools import find_packages, setup
setup( setup(
name='onlylegs', name='onlylegs',
version='110123', version='130123',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
install_requires=[ install_requires=[