mirror of
https://github.com/Derpy-Leggies/OnlyLegs.git
synced 2025-06-29 03:26:16 +00:00
Add support for different image directories
This changed the url for images, from file to media
This commit is contained in:
parent
8e54a3b4b6
commit
87a8254c73
7 changed files with 93 additions and 27 deletions
|
@ -4,16 +4,16 @@ Onlylegs - API endpoints
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import re
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from flask import Blueprint, send_from_directory, abort, flash, request, current_app
|
from flask import Blueprint, send_from_directory, abort, flash, request, current_app
|
||||||
from werkzeug.utils import secure_filename
|
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
from colorthief import ColorThief
|
from colorthief import ColorThief
|
||||||
|
|
||||||
from onlylegs.extensions import db
|
from onlylegs.extensions import db
|
||||||
from onlylegs.models import Post, Group, GroupJunction
|
from onlylegs.models import Post, Group, GroupJunction, User
|
||||||
from onlylegs.utils import metadata as mt
|
from onlylegs.utils import metadata as mt
|
||||||
from onlylegs.utils.generate_image import generate_thumbnail
|
from onlylegs.utils.generate_image import generate_thumbnail
|
||||||
|
|
||||||
|
@ -21,25 +21,24 @@ from onlylegs.utils.generate_image import generate_thumbnail
|
||||||
blueprint = Blueprint("api", __name__, url_prefix="/api")
|
blueprint = Blueprint("api", __name__, url_prefix="/api")
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/file/<file_name>", methods=["GET"])
|
@blueprint.route("/media/<path:path>", methods=["GET"])
|
||||||
def file(file_name):
|
def media(path):
|
||||||
"""
|
"""
|
||||||
Returns a file from the uploads folder
|
Returns a file from the uploads folder
|
||||||
r for resolution, 400x400 or thumb for thumbnail
|
r for resolution, thumb for thumbnail etc
|
||||||
|
e for extension, jpg, png etc
|
||||||
"""
|
"""
|
||||||
res = request.args.get("r", default=None, type=str) # Type of file (thumb, etc)
|
res = request.args.get("r", default=None, type=str)
|
||||||
ext = request.args.get("e", default=None, type=str) # File extension
|
ext = request.args.get("e", default=None, type=str)
|
||||||
file_name = secure_filename(file_name) # Sanitize file name
|
# path = secure_filename(path)
|
||||||
|
|
||||||
# if no args are passed, return the raw file
|
# if no args are passed, return the raw file
|
||||||
if not res and not ext:
|
if not res and not ext:
|
||||||
if not os.path.exists(
|
if not os.path.exists(os.path.join(current_app.config["MEDIA_FOLDER"], path)):
|
||||||
os.path.join(current_app.config["UPLOAD_FOLDER"], file_name)
|
|
||||||
):
|
|
||||||
abort(404)
|
abort(404)
|
||||||
return send_from_directory(current_app.config["UPLOAD_FOLDER"], file_name)
|
return send_from_directory(current_app.config["MEDIA_FOLDER"], path)
|
||||||
|
|
||||||
thumb = generate_thumbnail(file_name, res, ext)
|
thumb = generate_thumbnail(path, res, ext)
|
||||||
if not thumb:
|
if not thumb:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
@ -197,3 +196,69 @@ def delete_group():
|
||||||
|
|
||||||
flash(["Group yeeted!", "1"])
|
flash(["Group yeeted!", "1"])
|
||||||
return ":3"
|
return ":3"
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route("/user/picture/<int:user_id>", methods=["POST"])
|
||||||
|
def user_picture(user_id):
|
||||||
|
"""
|
||||||
|
Returns the profile of a user
|
||||||
|
"""
|
||||||
|
user = db.get_or_404(User, user_id)
|
||||||
|
file = request.files["file"]
|
||||||
|
|
||||||
|
# If no image is uploaded, return 404 error
|
||||||
|
if not file:
|
||||||
|
return abort(404)
|
||||||
|
elif user.id != current_user.id:
|
||||||
|
return abort(403)
|
||||||
|
|
||||||
|
# Get file extension, generate random name and set file path
|
||||||
|
img_ext = pathlib.Path(file.filename).suffix.replace(".", "").lower()
|
||||||
|
img_name = str(user.id)
|
||||||
|
img_path = os.path.join(current_app.config["PFP_FOLDER"], img_name + "." + img_ext)
|
||||||
|
|
||||||
|
# Check if file extension is allowed
|
||||||
|
if img_ext not in current_app.config["ALLOWED_EXTENSIONS"].keys():
|
||||||
|
logging.info("File extension not allowed: %s", img_ext)
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
if user.picture:
|
||||||
|
os.remove(os.path.join(current_app.config["PFP_FOLDER"], user.picture))
|
||||||
|
|
||||||
|
# Save file
|
||||||
|
try:
|
||||||
|
file.save(img_path)
|
||||||
|
except OSError as err:
|
||||||
|
logging.info("Error saving file %s because of %s", img_path, err)
|
||||||
|
abort(500)
|
||||||
|
|
||||||
|
img_colors = ColorThief(img_path).get_color() # Get color palette
|
||||||
|
|
||||||
|
# Save to database
|
||||||
|
user.colour = img_colors
|
||||||
|
user.picture = str(img_name + "." + img_ext)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return "Gwa Gwa" # Return something so the browser doesn't show an error
|
||||||
|
|
||||||
|
@blueprint.route("/user/username/<int:user_id>", methods=["POST"])
|
||||||
|
def user_username(user_id):
|
||||||
|
"""
|
||||||
|
Returns the profile of a user
|
||||||
|
"""
|
||||||
|
user = db.get_or_404(User, user_id)
|
||||||
|
new_name = request.form["name"]
|
||||||
|
|
||||||
|
username_regex = re.compile(r"\b[A-Za-z0-9._-]+\b")
|
||||||
|
|
||||||
|
# Validate the form
|
||||||
|
if not new_name or not username_regex.match(new_name):
|
||||||
|
abort(400)
|
||||||
|
elif user.id != current_user.id:
|
||||||
|
return abort(403)
|
||||||
|
|
||||||
|
# Save to database
|
||||||
|
user.username = new_name
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return "Gwa Gwa" # Return something so the browser doesn't show an error
|
|
@ -36,6 +36,7 @@ WEBSITE_CONF = conf["website"]
|
||||||
UPLOAD_FOLDER = os.path.join(user_dir, "media", "uploads")
|
UPLOAD_FOLDER = os.path.join(user_dir, "media", "uploads")
|
||||||
CACHE_FOLDER = os.path.join(user_dir, "media", "cache")
|
CACHE_FOLDER = os.path.join(user_dir, "media", "cache")
|
||||||
PFP_FOLDER = os.path.join(user_dir, "media", "pfp")
|
PFP_FOLDER = os.path.join(user_dir, "media", "pfp")
|
||||||
|
MEDIA_FOLDER = os.path.join(user_dir, "media")
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
INSTANCE_DIR = instance_dir
|
INSTANCE_DIR = instance_dir
|
||||||
|
|
|
@ -209,8 +209,7 @@
|
||||||
.navigation {
|
.navigation {
|
||||||
background-color: rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}) !important;
|
background-color: rgb({{ images.0.colours.0.0 }}, {{ images.0.colours.0.1 }}, {{ images.0.colours.0.2 }}) !important;
|
||||||
}
|
}
|
||||||
.navigation-item > svg {
|
.navigation-item > i {
|
||||||
fill: {{ text_colour }} !important;
|
|
||||||
color: {{ text_colour }} !important;
|
color: {{ text_colour }} !important;
|
||||||
}
|
}
|
||||||
.navigation-item.selected::before {
|
.navigation-item.selected::before {
|
||||||
|
@ -222,7 +221,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if images %}
|
{% if images %}
|
||||||
<div class="banner">
|
<div class="banner">
|
||||||
<img src="{{ url_for('api.file', file_name=images.0.filename ) }}?r=prev" onload="imgFade(this)" style="opacity:0;" alt="{% if images.0.alt %}{{ images.0.alt }}{% else %}Group Banner{% endif %}"/>
|
<img src="{{ url_for('api.media', path='uploads/' + images.0.filename ) }}?r=prev" onload="imgFade(this)" style="opacity:0;" alt="{% if images.0.alt %}{{ images.0.alt }}{% else %}Group Banner{% endif %}"/>
|
||||||
<span class="banner-filter"></span>
|
<span class="banner-filter"></span>
|
||||||
<div class="banner-content">
|
<div class="banner-content">
|
||||||
<p class="banner-info"><a href="{{ url_for('profile.profile', id=group.author.id) }}" class="link">By {{ group.author.username }}</a></p>
|
<p class="banner-info"><a href="{{ url_for('profile.profile', id=group.author.id) }}" class="link">By {{ group.author.username }}</a></p>
|
||||||
|
@ -268,7 +267,7 @@
|
||||||
<div class="image-filter">
|
<div class="image-filter">
|
||||||
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
|
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
|
||||||
</div>
|
</div>
|
||||||
<img alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
<img alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" data-src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{% block page_index %}
|
{% block page_index %}
|
||||||
{% if return_page %}?page={{ return_page }}{% endif %}{% endblock %}
|
{% if return_page %}?page={{ return_page }}{% endif %}{% endblock %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<meta property="og:image" content="{{ url_for('api.file', file_name=image.filename) }}"/>
|
<meta property="og:image" content="{{ url_for('api.media', path='uploads/' + image.filename) }}"/>
|
||||||
<meta name="theme-color" content="rgb({{ image.colours.0.0 }}{{ image.colours.0.1 }}{{ image.colours.0.2 }})"/>
|
<meta name="theme-color" content="rgb({{ image.colours.0.0 }}{{ image.colours.0.1 }}{{ image.colours.0.2 }})"/>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
@ -82,7 +82,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="background">
|
<div class="background">
|
||||||
<img src="{{ url_for('api.file', file_name=image.filename) }}?r=prev" alt="{{ image.alt }}" onload="imgFade(this)" style="opacity:0;"/>
|
<img src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=prev" alt="{{ image.alt }}" onload="imgFade(this)" style="opacity:0;"/>
|
||||||
<span></span>
|
<span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
<div class="image-block">
|
<div class="image-block">
|
||||||
<div class="image-container">
|
<div class="image-container">
|
||||||
<img
|
<img
|
||||||
src="{{ url_for('api.file', file_name=image.filename) }}?r=prev"
|
src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=prev"
|
||||||
alt="{{ image.alt }}"
|
alt="{{ image.alt }}"
|
||||||
onload="imgFade(this)"
|
onload="imgFade(this)"
|
||||||
style="opacity: 0;"
|
style="opacity: 0;"
|
||||||
|
@ -107,7 +107,7 @@
|
||||||
<div>
|
<div>
|
||||||
<button class="pill-item" onclick="fullscreen()" id="fullscreenImage"><i class="ph ph-info"></i></button>
|
<button class="pill-item" onclick="fullscreen()" id="fullscreenImage"><i class="ph ph-info"></i></button>
|
||||||
<button class="pill-item" onclick="imageShare()"><i class="ph ph-export"></i></button>
|
<button class="pill-item" onclick="imageShare()"><i class="ph ph-export"></i></button>
|
||||||
<a class="pill-item" href="{{ url_for('api.file', file_name=image.filename) }}" download onclick="addNotification('Download started!', 4)"><i class="ph ph-file-arrow-down"></i></a>
|
<a class="pill-item" href="{{ url_for('api.media', path='uploads/' + image.filename) }}" download onclick="addNotification('Download started!', 4)"><i class="ph ph-file-arrow-down"></i></a>
|
||||||
</div>
|
</div>
|
||||||
{% if current_user.id == image.author.id %}
|
{% if current_user.id == image.author.id %}
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
<div class="image-filter">
|
<div class="image-filter">
|
||||||
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
|
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
|
||||||
</div>
|
</div>
|
||||||
<img fetchpriority="low" alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
<img fetchpriority="low" alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" data-src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load"/>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -126,7 +126,7 @@
|
||||||
<div class="images size-{{ group.images|length }}">
|
<div class="images size-{{ group.images|length }}">
|
||||||
{% if group.images|length > 0 %}
|
{% if group.images|length > 0 %}
|
||||||
{% for image in group.images %}
|
{% for image in group.images %}
|
||||||
<img data-src="{{ url_for('api.file', file_name=image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load" class="data-{{ loop.index }}" {% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}/>
|
<img data-src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=thumb" onload="this.classList.add('loaded');" id="lazy-load" class="data-{{ loop.index }}" {% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}/>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<img src="{{ url_for('static', filename='error.png') }}" class="loaded" alt="Error thumbnail"/>
|
<img src="{{ url_for('static', filename='error.png') }}" class="loaded" alt="Error thumbnail"/>
|
||||||
|
|
|
@ -4,11 +4,11 @@ Tools for generating images and thumbnails
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
from onlylegs.config import UPLOAD_FOLDER, CACHE_FOLDER
|
from onlylegs.config import MEDIA_FOLDER, CACHE_FOLDER
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
|
|
||||||
def generate_thumbnail(file_name, resolution, ext=None):
|
def generate_thumbnail(file_path, resolution, ext=None):
|
||||||
"""
|
"""
|
||||||
Image thumbnail generator
|
Image thumbnail generator
|
||||||
Uses PIL to generate a thumbnail of the image and saves it to the cache directory
|
Uses PIL to generate a thumbnail of the image and saves it to the cache directory
|
||||||
|
@ -21,6 +21,7 @@ def generate_thumbnail(file_name, resolution, ext=None):
|
||||||
os.makedirs(CACHE_FOLDER)
|
os.makedirs(CACHE_FOLDER)
|
||||||
|
|
||||||
# no sussy business
|
# no sussy business
|
||||||
|
file_name = os.path.basename(file_path)
|
||||||
file_name, file_ext = secure_filename(file_name).rsplit(".")
|
file_name, file_ext = secure_filename(file_name).rsplit(".")
|
||||||
if not ext:
|
if not ext:
|
||||||
ext = file_ext.strip(".")
|
ext = file_ext.strip(".")
|
||||||
|
@ -44,11 +45,11 @@ def generate_thumbnail(file_name, resolution, ext=None):
|
||||||
return os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}")
|
return os.path.join(CACHE_FOLDER, f"{file_name}_{res_x}x{res_y}.{ext}")
|
||||||
|
|
||||||
# Check if image exists in the uploads directory
|
# Check if image exists in the uploads directory
|
||||||
if not os.path.exists(os.path.join(UPLOAD_FOLDER, f"{file_name}.{file_ext}")):
|
if not os.path.exists(os.path.join(MEDIA_FOLDER, file_path, f"{file_name}.{file_ext}")):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Open image and rotate it based on EXIF data and get ICC profile so colors are correct
|
# Open image and rotate it based on EXIF data and get ICC profile so colors are correct
|
||||||
image = Image.open(os.path.join(UPLOAD_FOLDER, f"{file_name}.{file_ext}"))
|
image = Image.open(os.path.join(MEDIA_FOLDER, file_path, f"{file_name}.{file_ext}"))
|
||||||
image_icc = image.info.get("icc_profile")
|
image_icc = image.info.get("icc_profile")
|
||||||
img_x, img_y = image.size
|
img_x, img_y = image.size
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue