import hashlib import json import logging import mimetypes import os import re import shutil import subprocess from datetime import datetime, timezone from pathlib import Path from urllib.parse import urlencode import imagehash from django.conf import settings from django.http import FileResponse, Http404, HttpResponse from django.utils.cache import get_conditional_response from django.utils.http import http_date from django.utils.text import get_valid_filename from PIL import Image, ImageOps from .models import MediaItem, MediaLocation from .signing_urls import sign_payload logger = logging.getLogger(__name__) HASH_FIELDS = ("ahash", "dhash", "phash", "whash") IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".apng", ".webp"} ALLOWED_EXTENSIONS = { ".jpg", ".jpeg", ".png", ".gif", ".apng", ".webp", ".mp4", ".webm", } VIDEO_EXTENSIONS = {".mp4", ".webm"} UPLOAD_FILE_SALT = "j621.upload-file" MEDIA_FILE_SALT = "j621.media-file" CHUNK_SIZE = 1024 * 1024 RANGE_RE = re.compile(r"bytes=(\d*)-(\d*)$") # Versioned media URLs are immutable, so they may sit in the browser cache for # as long as the signature is guaranteed to stay valid (7 days). MEDIA_CACHE_SECONDS = 6 * 86400 # Staged uploads and similarity files can be deleted at any moment. TEMP_CACHE_SECONDS = 3600 def compute_md5(path): digest = hashlib.md5() with open(path, "rb") as handle: for chunk in iter(lambda: handle.read(CHUNK_SIZE), b""): digest.update(chunk) return digest.hexdigest() def index_file(path, folder): """Index one file into MediaItem/MediaLocation. Returns (item, created_item, location, created_location). """ path = Path(path) folder = Path(folder) stat = path.stat() md5 = compute_md5(path) item, created_item = MediaItem.objects.get_or_create( md5=md5, defaults={"size": stat.st_size} ) if not created_item and item.size != stat.st_size: item.size = stat.st_size item.save(update_fields=["size", "updated_at"]) location, created_location = MediaLocation.objects.update_or_create( item=item, path=str(path), defaults={ "rel_path": str(path.relative_to(folder)), "mtime": stat.st_mtime, }, ) return item, created_item, location, created_location def signed_media_url(item, user, action="raw", request=None): """Media URL that /