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/; 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