112 lines
3.2 KiB
Python
112 lines
3.2 KiB
Python
import os
|
|
import uuid
|
|
from datetime import datetime
|
|
from PIL import Image
|
|
from flask import current_app, url_for
|
|
from app import db
|
|
from .models import Media
|
|
from plugins.plant.models import Plant
|
|
|
|
|
|
def get_upload_path():
|
|
"""
|
|
Return (full_disk_path, subdir) based on UTC date,
|
|
creating directories if needed.
|
|
e.g. ('/app/static/uploads/2025/06/09', '2025/06/09')
|
|
"""
|
|
base = current_app.config.get("UPLOAD_FOLDER", "static/uploads")
|
|
now = datetime.utcnow()
|
|
subdir = os.path.join(str(now.year), f"{now.month:02}", f"{now.day:02}")
|
|
full = os.path.join(base, subdir)
|
|
os.makedirs(full, exist_ok=True)
|
|
return full, subdir
|
|
|
|
|
|
def generate_random_filename(original_filename):
|
|
"""
|
|
Preserve extension, randomize base name.
|
|
"""
|
|
ext = os.path.splitext(original_filename)[1].lower()
|
|
return f"{uuid.uuid4().hex}{ext}"
|
|
|
|
|
|
def strip_metadata_and_save(source_file, destination_path):
|
|
"""
|
|
Opens an image with Pillow, strips EXIF metadata, and saves it.
|
|
Supports common formats (JPEG, PNG).
|
|
"""
|
|
with Image.open(source_file) as img:
|
|
data = list(img.getdata())
|
|
clean_image = Image.new(img.mode, img.size)
|
|
clean_image.putdata(data)
|
|
clean_image.save(destination_path)
|
|
|
|
|
|
def generate_image_url(path):
|
|
"""
|
|
If path is set, route through /media/files/<path>; otherwise
|
|
return a placehold.co URL sized to STANDARD_IMG_SIZE.
|
|
"""
|
|
if path:
|
|
return url_for("media.media_file", filename=path)
|
|
w, h = current_app.config.get("STANDARD_IMG_SIZE", (300, 200))
|
|
return f"https://placehold.co/{w}x{h}"
|
|
|
|
|
|
def save_media_file(file_storage, uploader_id, related_model=None, related_uuid=None):
|
|
"""
|
|
- file_storage: Werkzeug FileStorage
|
|
- uploader_id: current_user.id
|
|
- related_model: e.g. 'plant'
|
|
- related_uuid: the Plant.uuid string
|
|
Returns the new Media instance.
|
|
"""
|
|
full_path, subdir = get_upload_path()
|
|
filename = generate_random_filename(file_storage.filename)
|
|
disk_path = os.path.join(full_path, filename)
|
|
file_storage.save(disk_path)
|
|
|
|
media = Media(
|
|
file_url=os.path.join(subdir, filename).replace("\\", "/"),
|
|
uploader_id=uploader_id
|
|
)
|
|
|
|
# Associate to plant if requested
|
|
if related_model == "plant" and related_uuid:
|
|
plant = Plant.query.filter_by(uuid=related_uuid).first()
|
|
if plant:
|
|
media.plant_id = plant.id
|
|
|
|
db.session.add(media)
|
|
db.session.commit()
|
|
return media
|
|
|
|
|
|
def delete_media_file(media):
|
|
"""
|
|
Remove file from disk and delete DB record.
|
|
"""
|
|
base = current_app.config.get("UPLOAD_FOLDER", "static/uploads")
|
|
path = os.path.join(base, media.file_url)
|
|
try:
|
|
os.remove(path)
|
|
except OSError:
|
|
pass
|
|
db.session.delete(media)
|
|
db.session.commit()
|
|
|
|
|
|
def rotate_media_file(media, angle=-90):
|
|
"""
|
|
Rotate the file on disk (in place) and leave DB record intact.
|
|
"""
|
|
base = current_app.config.get("UPLOAD_FOLDER", "static/uploads")
|
|
path = os.path.join(base, media.file_url)
|
|
try:
|
|
with Image.open(path) as img:
|
|
rotated = img.rotate(angle, expand=True)
|
|
rotated.save(path)
|
|
except Exception:
|
|
pass
|
|
# no DB changes needed
|