Backend: - TempUpload model: staged files (pending / visual_match / completed / error) with resolution, e621 payload, custom metadata and IQDB data - Files land in a temp folder and only move into the watched library folder once resolved; duplicates resolve immediately without a copy - Endpoints: stage (multipart), list, retrieve, temp file, IQDB save, resolve (link to post or custom metadata), discard/dismiss - cleanup_temp_uploads command for old staged files - Replaces the old direct-to-library upload endpoint Frontend: - Upload page is now a three-column board (Pending & Unmatched / Visual Similarity Detected / Auto-uploaded & Indexed) - After upload: MD5s are batch-checked against e621 and matches auto-complete with full post metadata; remaining files run through IQDB and move to the similarity column when candidates exist - Metadata modal with IQDB candidates, post-ID linking and custom tags/rating/notes; discard and dismiss actions - e621 client gains fetchPostsByMd5 and iqdbSearch helpers Roadmap updated with the completed upload items.
129 lines
3.7 KiB
Python
129 lines
3.7 KiB
Python
import os
|
|
|
|
from rest_framework import serializers
|
|
|
|
from .models import MediaItem, MediaLocation, TempUpload
|
|
from .services import VIDEO_EXTENSIONS
|
|
|
|
|
|
class MediaLocationSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = MediaLocation
|
|
fields = ["id", "path", "rel_path", "mtime"]
|
|
|
|
|
|
class MediaItemSerializer(serializers.ModelSerializer):
|
|
tags = serializers.ListField(
|
|
child=serializers.CharField(max_length=100), required=False
|
|
)
|
|
rating = serializers.ChoiceField(choices=["", "s", "q", "e"], required=False)
|
|
locations = MediaLocationSerializer(many=True, read_only=True)
|
|
j_id = serializers.SerializerMethodField()
|
|
display_rating = serializers.SerializerMethodField()
|
|
filename = serializers.SerializerMethodField()
|
|
extension = serializers.SerializerMethodField()
|
|
kind = serializers.SerializerMethodField()
|
|
uploaded_by = serializers.SerializerMethodField()
|
|
uploaded_by_id = serializers.IntegerField(read_only=True)
|
|
|
|
class Meta:
|
|
model = MediaItem
|
|
fields = [
|
|
"id",
|
|
"j_id",
|
|
"md5",
|
|
"size",
|
|
"rating",
|
|
"display_rating",
|
|
"tags",
|
|
"notes",
|
|
"filename",
|
|
"extension",
|
|
"kind",
|
|
"uploaded_by",
|
|
"uploaded_by_id",
|
|
"e621_post_id",
|
|
"e621_data",
|
|
"locations",
|
|
"created_at",
|
|
"updated_at",
|
|
]
|
|
read_only_fields = [
|
|
"id",
|
|
"j_id",
|
|
"md5",
|
|
"size",
|
|
"display_rating",
|
|
"filename",
|
|
"extension",
|
|
"kind",
|
|
"uploaded_by",
|
|
"uploaded_by_id",
|
|
"e621_post_id",
|
|
"e621_data",
|
|
"locations",
|
|
"created_at",
|
|
"updated_at",
|
|
]
|
|
|
|
def get_j_id(self, obj):
|
|
return f"J-{obj.id}"
|
|
|
|
def get_display_rating(self, obj):
|
|
if obj.rating:
|
|
return obj.rating
|
|
data = obj.e621_data or {}
|
|
rating = data.get("rating") if isinstance(data, dict) else None
|
|
return rating if rating in {"s", "q", "e"} else ""
|
|
|
|
def get_uploaded_by(self, obj):
|
|
return obj.uploaded_by.username if obj.uploaded_by else None
|
|
|
|
def _first_location(self, obj):
|
|
locations = list(obj.locations.all())
|
|
return locations[0] if locations else None
|
|
|
|
def get_filename(self, obj):
|
|
location = self._first_location(obj)
|
|
return os.path.basename(location.rel_path) if location else obj.md5
|
|
|
|
def get_extension(self, obj):
|
|
return os.path.splitext(self.get_filename(obj))[1].lstrip(".").lower()
|
|
|
|
def get_kind(self, obj):
|
|
return "video" if "." + self.get_extension(obj) in VIDEO_EXTENSIONS else "image"
|
|
|
|
|
|
class TempUploadSerializer(serializers.ModelSerializer):
|
|
temp_id = serializers.UUIDField(source="id", read_only=True)
|
|
library_j_id = serializers.SerializerMethodField()
|
|
file_url = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = TempUpload
|
|
fields = [
|
|
"temp_id",
|
|
"original_filename",
|
|
"md5",
|
|
"size",
|
|
"status",
|
|
"resolution",
|
|
"e621_post_id",
|
|
"e621_data",
|
|
"custom_rating",
|
|
"custom_tags",
|
|
"custom_notes",
|
|
"iqdb_data",
|
|
"library_j_id",
|
|
"file_url",
|
|
"created_at",
|
|
"updated_at",
|
|
]
|
|
read_only_fields = fields
|
|
|
|
def get_library_j_id(self, obj):
|
|
return f"J-{obj.library_item_id}" if obj.library_item_id else None
|
|
|
|
def get_file_url(self, obj):
|
|
return f"/api/uploads/{obj.id}/file/" if obj.file else None
|