Files
2026-05-23 17:17:56 -07:00

752 lines
22 KiB
Go

package routes
import (
"bytes"
"context"
"encoding/json"
"fmt"
"gifuu/tools"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
)
const (
VIDEO_MAX_WIDTH = 3840
VIDEO_MAX_HEIGHT = 2160
IMAGE_MAX_WIDTH = 7680
IMAGE_MAX_HEIGHT = 4320
MEDIA_MIN_WIDTH = 64
MEDIA_MIN_HEIGHT = 64
MEDIA_MAX_DURATION = 62
MEDIA_COLOR_SPACE = "yuv420p"
MEDIA_COLOR_RANGE = "tv"
MEDIA_FILENAME_PREVIEW = "preview.avif"
MEDIA_FILENAME_STANDARD = "standard.avif"
MEDIA_FILENAME_ALPHA = "alpha.webm"
MEDIA_FILENAME_AUDIO = "standard.ogg"
MEDIA_BACKGROUND = "#ffffff"
VIDEO_ENCODE_CODEC = "libsvtav1"
VIDEO_ENCODE_EFFORT = "7"
VIDEO_ENCODE_PARAMS = "lp=2"
VIDEO_FILTERS = ""
VIDEO_KEYFRAME_INTERVAL = 6
VIDEO_PREVIEW_MAX_DURATION = 8
VIDEO_PREVIEW_FPS = 16
VIDEO_PREVIEW_SIZE = 240
VIDEO_PREVIEW_QUALITY = "55"
VIDEO_STANDARD_FPS = 60
VIDEO_STANDARD_SIZE = 720
VIDEO_STANDARD_QUALITY = "50"
IMAGE_ENCODE_CODEC = "libsvtav1"
IMAGE_ENCODE_EFFORT = "7"
IMAGE_ENCODE_QUALITY = "45"
IMAGE_ENCODE_PARAMS = "lp=2"
IMAGE_FILTERS = "tpad=stop_mode=clone:stop_duration=1,"
IMAGE_LARGE_SIZE = 2160
IMAGE_PREVIEW_SIZE = 240
)
func ternary[T any](cond bool, a, b T) T {
if cond {
return a
}
return b
}
func POST_Uploads(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// --- Update Request Lifetime ---
sse, err := tools.NewEventHelper(ctx, w, r)
if err != nil {
tools.SendServerError(w, r, err)
}
rc := http.NewResponseController(w)
if err := rc.SetWriteDeadline(time.Now().Add(10 * time.Minute)); err != nil {
sse.SendServerError(err)
return
}
// --------------------------------------------------------------------------------
// --- [ Prevent Abuse ] ----------------------------------------------------------
// --------------------------------------------------------------------------------
var (
ClientAddress = tools.RequestAddressHash(r)
ClientToken = tools.RequestToken()
ClientLength = max(0, r.ContentLength)
)
{
// --- Request Upload Capacity ---
if ClientLength == 0 {
sse.SendClientError(tools.ERROR_BODY_EMPTY)
return
}
if ClientLength > int64(tools.LIMIT_FILE+tools.LIMIT_JSON) {
sse.SendClientError(tools.ERROR_BODY_TOO_LARGE)
return
}
if !tools.SEMA_UPLOADS.TryAcquire(ClientLength) {
sse.SendClientError(tools.ERROR_SERVER_RESOURCES_EXHAUSTED)
return
}
defer tools.SEMA_UPLOADS.Release(ClientLength)
// --- Check Ban List ---
var SubjectCount int
err := tools.Database.
QueryRow(ctx, "SELECT COUNT(*) FROM gifuu.mod_banned WHERE address_hash = $1", ClientAddress).
Scan(&SubjectCount)
if err != nil {
sse.SendServerError(err)
return
}
if SubjectCount > 0 {
sse.SendClientError(tools.ERROR_GENERIC_FORBIDDEN)
return
}
}
// --------------------------------------------------------------------------------
// --- [ Parse Incoming Request ] -------------------------------------------------
// --------------------------------------------------------------------------------
var (
TempID = tools.RequestSnowflake()
TempIDString = strconv.FormatInt(TempID, 10)
TempSuccess = false
TempUpload *os.File
TempLogger *os.File
Body struct {
Title string `json:"title"`
Tags []string `json:"tags"`
}
PathUpload = filepath.Join(tools.STORAGE_DISK_TEMP, TempIDString+".bin")
PathLogger = filepath.Join(tools.STORAGE_DISK_TEMP, TempIDString+".log")
PathAlpha = filepath.Join(tools.STORAGE_DISK_TEMP, TempIDString+"_"+MEDIA_FILENAME_ALPHA)
PathPreview = filepath.Join(tools.STORAGE_DISK_TEMP, TempIDString+"_"+MEDIA_FILENAME_PREVIEW)
PathStandard = filepath.Join(tools.STORAGE_DISK_TEMP, TempIDString+"_"+MEDIA_FILENAME_STANDARD)
PathAudio = filepath.Join(tools.STORAGE_DISK_TEMP, TempIDString+"_"+MEDIA_FILENAME_AUDIO)
)
{
// --- Create Temporary Files ---
flags := os.O_CREATE | os.O_TRUNC | os.O_WRONLY
if f, err := os.OpenFile(PathUpload, flags, tools.FILE_PUBLIC); err != nil {
sse.SendServerError(err)
return
} else {
TempUpload = f
defer os.Remove(PathUpload)
defer f.Close()
}
if f, err := os.OpenFile(PathLogger, flags, tools.FILE_PRIVATE); err != nil {
sse.SendServerError(err)
return
} else {
TempLogger = f
defer f.Close()
}
defer os.Remove(PathAlpha)
defer os.Remove(PathPreview)
defer os.Remove(PathStandard)
defer os.Remove(PathAudio)
// --- Parse Form Body ---
var haveFile, haveData bool
reader, err := r.MultipartReader()
if err != nil {
sse.SendClientError(tools.ERROR_BODY_INVALID_DATA)
return
}
for {
part, err := reader.NextPart()
if err != nil {
break
}
// --- Parse Incoming Metadata ---
if part.FormName() == "data" {
if haveData {
sse.SendClientError(tools.ERROR_BODY_INVALID_FIELD)
return
}
haveData = true
if err := tools.ParseJSON(part, &Body); err != nil {
sse.SendClientError(tools.ERROR_BODY_INVALID_FIELD)
break
}
// Validate Struct
if normal, ok := tools.NormalizeTitle(Body.Title); !ok {
sse.SendClientError(tools.ERROR_BODY_INVALID_FIELD)
return
} else {
Body.Title = normal
}
indexTags := make(map[string]struct{}, len(Body.Tags))
for i, given := range Body.Tags {
normal, ok := tools.NormalizeTag(given)
if _, exists := indexTags[normal]; exists || !ok {
sse.SendClientError(tools.ERROR_BODY_INVALID_FIELD)
return
}
indexTags[normal] = struct{}{}
Body.Tags[i] = normal
}
fmt.Fprintf(TempLogger, "Collected JSON : %s\n", Body)
continue
}
// --- Store Incoming Upload ---
if part.FormName() == "file" {
if haveFile {
sse.SendClientError(tools.ERROR_BODY_INVALID_FIELD)
return
}
haveFile = true
// Check File Extension
mediaAccept := false
mediaType := part.Header.Get("Content-Type")
for _, t := range tools.LIMIT_MIME_TYPE {
if strings.EqualFold(t, mediaType) {
mediaAccept = true
break
}
}
if !mediaAccept {
sse.SendClientError(tools.ERROR_BODY_INVALID_CONTENT_TYPE)
return
}
// Copy File
mediaSize, err := io.Copy(TempUpload, io.LimitReader(part, ClientLength))
if err != nil {
sse.SendClientError(tools.ERROR_BODY_INVALID_DATA)
return
}
TempUpload.Close()
fmt.Fprintf(TempLogger,
"Collected File : %s (Type: %s) (Size: %db)\n",
mediaType, part.FileName(), mediaSize,
)
continue
}
sse.SendClientError(tools.ERROR_BODY_INVALID_FIELD)
return
}
if !haveFile || !haveData {
sse.SendClientError(tools.ERROR_BODY_INVALID_FIELD)
return
}
sse.SendJSON("id", TempIDString)
}
// --------------------------------------------------------------------------------
// --- [ Media Validation ] -------------------------------------------------------
// --------------------------------------------------------------------------------
var (
ProbeResults tools.ProbeResults // Probe Results
ProbeVideo *tools.ProbeStream // Relevant Video Stream
ProbeAudio *tools.ProbeStream // Relevant Audio Stream
MediaSticker bool // Is Static?
MediaFramerate int // Approximate Framerate
MediaHeight int // Approximate Scaled Height
MediaWidth int // Approximate Scaled Width
MediaRating float32 // Worst value from Classification
)
{
sse.SendJSON("step", map[string]any{"id": "PROBE_QUEUE", "message": "Queued for Probing"})
t := time.Now()
// --- Acquire Probe Slot ---
if err := tools.SEMA_PROBES.Acquire(ctx, 1); err != nil {
return
}
defer tools.SEMA_PROBES.Release(1)
// --- Probe Media Stream ---
sse.SendJSON("step", map[string]any{"id": "PROBE_START", "message": "Probing"})
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd := exec.CommandContext(ctx, "ffprobe",
"-hide_banner",
"-loglevel", "verbose",
"-print_format", "json",
"-show_streams",
"-i", PathUpload,
)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
fmt.Fprintf(TempLogger, "\n%s\n%s\nProbing completed in %s\n\n",
// extra newline for proper log padding
stdout.String(),
stderr.String(),
time.Since(t),
)
if ctx.Err() == context.Canceled {
return
}
if err != nil {
sse.SendServerError(err)
return
}
if err := json.Unmarshal(stdout.Bytes(), &ProbeResults); err != nil {
sse.SendServerError(err)
return
}
// --- Find Media Stream ---
// isVideo: Only video streams (AV1, MP4, etc.)
// isImage: Only select image streams if it's the only stream available
for _, s := range ProbeResults.Streams {
isVideo := (s.CodecType == "video")
isImage := (s.CodecType == "image" && len(ProbeResults.Streams) == 1)
isValid := true &&
s.Width >= MEDIA_MIN_WIDTH && s.Height >= MEDIA_MIN_HEIGHT &&
float64(s.Duration) <= float64(MEDIA_MAX_DURATION) &&
ternary(s.NumberFrames < 2,
s.Width < IMAGE_MAX_WIDTH && s.Height < IMAGE_MAX_HEIGHT,
s.Width < VIDEO_MAX_WIDTH && s.Height < VIDEO_MAX_HEIGHT,
)
if isValid && (isVideo || isImage) && (ProbeVideo == nil || s.Duration > ProbeVideo.Duration) {
ProbeVideo = &s
}
isAudio := (s.CodecType == "audio")
if isAudio {
ProbeAudio = &s
}
}
if ProbeVideo == nil {
sse.SendClientError(tools.ERROR_MEDIA_INVALID)
return
}
// --- Detect Properties ---
// Remove Audio from Stickers
MediaSticker = (ProbeVideo.NumberFrames < 2)
if MediaSticker {
ProbeAudio = nil
}
// Approximate Video Properties
d := ternary(MediaSticker, IMAGE_LARGE_SIZE, VIDEO_STANDARD_SIZE)
s := float64(min(ProbeVideo.Height, d)) / float64(ProbeVideo.Height)
MediaWidth = int(float64(ProbeVideo.Width)*s) &^ 1
MediaHeight = int(float64(ProbeVideo.Height)*s) &^ 1
MediaFramerate = ternary(MediaSticker, 1, min(int(ProbeVideo.RFrameRate), VIDEO_STANDARD_FPS))
}
// --------------------------------------------------------------------------------
// --- [ Media Processing ] -------------------------------------------------------
// --------------------------------------------------------------------------------
{
sse.SendJSON("step", map[string]any{"id": "ENCODE_QUEUE", "message": "Queued for Processing"})
t := time.Now()
// --- Acquire Upload Slot ---
if err := tools.SEMA_ENCODES.Acquire(ctx, 1); err != nil {
return
}
defer tools.SEMA_ENCODES.Release(1)
// --- Prepare For Encoding ---
sse.SendJSON("step", map[string]any{"id": "ENCODE_START", "message": "Processing"})
args := []string{
"-loglevel", "verbose",
"-hide_banner",
"-stats",
"-i", PathUpload,
"-filter_complex", fmt.Sprintf(""+
"[0:%d]format=rgba[fg];[fg]split[fg1][fg2];[fg2]drawbox=x=0:y=0:w=iw:h=ih:color=%s:t=fill[bg];[bg][fg1]overlay[base];"+
"[base]split=4[v1][v2][v3][v4];"+
"[v1]%sscale=-2:%d:flags=lanczos,fps=%d[v1o];"+
"[v2]%sscale=-2:%d:flags=lanczos,fps=%d[v2o];"+
"[v3]%sscale=-2:%d:flags=lanczos,fps=%d[v3a];[v3a]format=rgba[v3f];[v3f]split[v3color][v3mask];[v3mask]alphaextract[v3ae];[v3color][v3ae]vstack[v3o];"+
"[v4]%sscale=%d:%d:flags=neighbor,fps=%d[v4o];",
// Import
ProbeVideo.Index,
MEDIA_BACKGROUND,
// Export: Preview
ternary(MediaSticker, IMAGE_FILTERS, VIDEO_FILTERS),
min(ProbeVideo.Height, ternary(MediaSticker, IMAGE_PREVIEW_SIZE, VIDEO_PREVIEW_SIZE)),
min(int(ProbeVideo.RFrameRate), ternary(MediaSticker, 1, VIDEO_PREVIEW_FPS)),
// Export: Standard
ternary(MediaSticker, IMAGE_FILTERS, VIDEO_FILTERS),
min(ProbeVideo.Height, ternary(MediaSticker, IMAGE_LARGE_SIZE, VIDEO_STANDARD_SIZE)),
min(int(ProbeVideo.RFrameRate), ternary(MediaSticker, 1, VIDEO_STANDARD_FPS)),
// Export: Alpha
ternary(MediaSticker, IMAGE_FILTERS, VIDEO_FILTERS),
min(ProbeVideo.Height, ternary(MediaSticker, IMAGE_LARGE_SIZE, VIDEO_STANDARD_SIZE)),
min(int(ProbeVideo.RFrameRate), ternary(MediaSticker, 1, VIDEO_STANDARD_FPS)),
// Export: Inference
IMAGE_FILTERS,
tools.MODEL_SIZE,
tools.MODEL_SIZE,
tools.MODEL_FRAMERATE,
),
// Export Preview
"-map", "[v1o]", "-an", "-sn",
"-c:v" /*----------*/, ternary(MediaSticker, IMAGE_ENCODE_CODEC, VIDEO_ENCODE_CODEC),
"-preset" /*-------*/, ternary(MediaSticker, IMAGE_ENCODE_EFFORT, VIDEO_ENCODE_EFFORT),
"-qp" /*-----------*/, ternary(MediaSticker, IMAGE_ENCODE_QUALITY, VIDEO_PREVIEW_QUALITY),
"-g" /*------------*/, strconv.Itoa(VIDEO_PREVIEW_FPS * VIDEO_KEYFRAME_INTERVAL),
"-pix_fmt" /*------*/, MEDIA_COLOR_SPACE,
"-color_range" /*--*/, MEDIA_COLOR_RANGE,
"-svtav1-params" /**/, ternary(MediaSticker, IMAGE_ENCODE_PARAMS, VIDEO_ENCODE_PARAMS),
"-frames:v" /*-----*/, ternary(MediaSticker, "1", strconv.Itoa(VIDEO_PREVIEW_FPS*VIDEO_PREVIEW_MAX_DURATION)),
"-loop" /*---------*/, ternary(MediaSticker, "1", "0"),
"-map_metadata" /*-*/, "-1",
"-metadata", ("gifuu_machine=" + tools.MACHINE_HOSTNAME),
"-metadata", ("gifuu_proverb=" + tools.MACHINE_PROVERB),
PathPreview,
// Export: Standard
"-map", "[v2o]", "-an", "-sn",
"-c:v" /*----------*/, ternary(MediaSticker, IMAGE_ENCODE_CODEC, VIDEO_ENCODE_CODEC),
"-preset" /*-------*/, ternary(MediaSticker, IMAGE_ENCODE_EFFORT, VIDEO_ENCODE_EFFORT),
"-qp" /*-----------*/, ternary(MediaSticker, IMAGE_ENCODE_QUALITY, VIDEO_STANDARD_QUALITY),
"-g" /*------------*/, strconv.Itoa(VIDEO_STANDARD_FPS * VIDEO_KEYFRAME_INTERVAL),
"-pix_fmt" /*------*/, MEDIA_COLOR_SPACE,
"-color_range" /*--*/, MEDIA_COLOR_RANGE,
"-svtav1-params" /**/, ternary(MediaSticker, IMAGE_ENCODE_PARAMS, VIDEO_ENCODE_PARAMS),
"-frames:v" /*-----*/, ternary(MediaSticker, "1", strconv.Itoa(VIDEO_STANDARD_FPS*MEDIA_MAX_DURATION)),
"-loop" /*---------*/, ternary(MediaSticker, "1", "0"),
"-map_metadata" /*-*/, "-1",
"-metadata", ("gifuu_machine=" + tools.MACHINE_HOSTNAME),
"-metadata", ("gifuu_proverb=" + tools.MACHINE_PROVERB),
PathStandard,
// Export: Alpha
"-map", "[v3o]", "-an", "-sn",
"-c:v" /*----------*/, ternary(MediaSticker, IMAGE_ENCODE_CODEC, VIDEO_ENCODE_CODEC),
"-preset" /*-------*/, ternary(MediaSticker, IMAGE_ENCODE_EFFORT, VIDEO_ENCODE_EFFORT),
"-qp" /*-----------*/, ternary(MediaSticker, IMAGE_ENCODE_QUALITY, VIDEO_STANDARD_QUALITY),
"-g" /*------------*/, strconv.Itoa(VIDEO_STANDARD_FPS * VIDEO_KEYFRAME_INTERVAL),
"-pix_fmt" /*------*/, MEDIA_COLOR_SPACE,
"-color_range" /*--*/, MEDIA_COLOR_RANGE,
"-svtav1-params" /**/, IMAGE_ENCODE_PARAMS,
"-frames:v" /*-----*/, ternary(MediaSticker, "1", strconv.Itoa(VIDEO_STANDARD_FPS*MEDIA_MAX_DURATION)),
"-loop" /*---------*/, ternary(MediaSticker, "1", "0"),
"-map_metadata" /*-*/, "-1",
"-metadata", ("gifuu_machine=" + tools.MACHINE_HOSTNAME),
"-metadata", ("gifuu_proverb=" + tools.MACHINE_PROVERB),
PathAlpha,
// Export: Moderation
"-map", "[v4o]", "-an", "-sn",
"-f" /*-------*/, "rawvideo",
"-fps_mode" /**/, "vfr",
"-pix_fmt" /*-*/, "rgb24",
"-",
}
if ProbeAudio != nil {
args = append(args,
"-map", fmt.Sprintf("0:%d", ProbeAudio.Index), "-vn", "-sn",
"-c:a", "libopus",
"-b:a", "64k",
"-af", "loudnorm=I=-16:TP=-2:LRA=11",
"-ar", "48000",
"-ac", "2",
"-map_metadata", "-1",
"-metadata", ("gifuu_machine=" + tools.MACHINE_HOSTNAME),
"-metadata", ("gifuu_proverb=" + tools.MACHINE_PROVERB),
PathAudio,
)
}
var encodeCtx, encodeCancel = context.WithCancel(ctx)
defer encodeCancel()
var stderr bytes.Buffer
cmd := exec.CommandContext(encodeCtx, "ffmpeg", args...)
cmd.Stderr = &stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
sse.SendServerError(err)
return
}
if err := cmd.Start(); err != nil {
sse.SendServerError(err)
return
}
// --- Auto Moderation ---
var (
classifyError error
classifyPercent float32
classifyAllowed = true
classifyComplete = make(chan struct{}, 1)
frameSize = (tools.MODEL_SIZE * tools.MODEL_SIZE * 3)
tensorSize = (tools.MODEL_FRAMERATE * frameSize)
tensorData = make([]float32, tensorSize)
frameData = make([]byte, tensorSize)
)
go func() {
defer close(classifyComplete)
defer io.Copy(io.Discard, stdout)
frameIndex := 0
for {
// Process Raw Frames for Model
n, err := io.ReadFull(stdout, frameData)
if err == io.EOF {
break
}
if err != nil && err != io.ErrUnexpectedEOF {
encodeCancel()
return
}
frameCount := n / frameSize
for i := 0; i < n; i++ {
tensorData[i] = float32(frameData[i]) / 255.0
}
// Classify Frames
logits, err := tools.ModelClassifyTensorBatch(
tensorData[:frameCount*frameSize],
frameCount,
)
if err != nil {
classifyError = err
encodeCancel()
return
}
// Calculate Results
for idx, results := range logits {
classifyPercent = (results.Hentai + results.Porn + (results.Sexy * 0.9))
classifyAllowed = (classifyPercent < tools.MODEL_THRESHOLD_DENY)
fmt.Fprintf(TempLogger,
"#%02d | D: %.2f | H: %.2f | N: %.2f | P: %.2f | S: %.2f | T: %.2f%% | OK: %t\n",
frameIndex+idx,
results.Drawing,
results.Hentai,
results.Neutral,
results.Porn,
results.Sexy,
classifyPercent,
classifyAllowed,
)
if classifyPercent > MediaRating {
MediaRating = classifyPercent
}
if !classifyAllowed {
encodeCancel()
return
}
}
frameIndex += frameCount
// Calculate Progress (Approximate)
sse.SendJSON("progress", map[string]any{
"percent": strconv.FormatFloat(
min(100, (float64(frameIndex)/float64(tools.MODEL_FRAMERATE))/float64(ProbeVideo.Duration)*100),
'f', 2, 64,
),
})
}
}()
// --- Wait for Results ---
err = cmd.Wait()
<-classifyComplete
fmt.Fprintf(TempLogger, "\n%s\nProcessing completed in %s\n",
stderr.String(),
time.Since(t),
)
if ctx.Err() == context.Canceled {
return
}
if !classifyAllowed {
sse.SendClientError(tools.ERROR_MEDIA_INAPPROPRIATE)
return
}
if classifyError != nil {
sse.SendServerError(classifyError)
return
}
if err != nil {
sse.SendServerError(err)
return
}
}
sse.SendJSON("step", map[string]any{"id": "SERVER_FINALIZE", "message": "Syncing"})
// --------------------------------------------------------------------------------
// --- [ Upload Objects ] ---------------------------------------------------------
// --------------------------------------------------------------------------------
{
var (
TargetDir = filepath.Join(tools.STORAGE_DISK_PUBLIC, TempIDString)
TargetAlpha = filepath.Join(TargetDir, MEDIA_FILENAME_ALPHA)
TargetPreview = filepath.Join(TargetDir, MEDIA_FILENAME_PREVIEW)
TargetStandard = filepath.Join(TargetDir, MEDIA_FILENAME_STANDARD)
TargetAudio = filepath.Join(TargetDir, MEDIA_FILENAME_AUDIO)
)
// --- Create Directory ---
if err := os.MkdirAll(TargetDir, tools.FILE_PUBLIC); err != nil {
sse.SendServerError(err)
return
}
defer func() {
if !TempSuccess {
if err := os.RemoveAll(TargetDir); err != nil {
tools.LoggerStorage.Log(tools.WARN, "Failed to delete incomplete files: %s", err)
}
}
}()
// --- Move Files ---
for _, op := range []struct {
ShouldCopy bool
PathSource string
PathTarget string
ContentType string
}{
{true, PathAlpha, TargetAlpha, "video/webm"},
{true, PathPreview, TargetPreview, "image/avif"},
{true, PathStandard, TargetStandard, "image/avif"},
{ProbeAudio != nil, PathAudio, TargetAudio, "audio/ogg"},
} {
if !op.ShouldCopy {
continue
}
s, err := os.Open(op.PathSource)
if err != nil {
sse.SendServerError(err)
return
}
defer s.Close()
t, err := os.OpenFile(op.PathTarget, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, tools.FILE_PUBLIC)
if err != nil {
sse.SendServerError(err)
return
}
defer t.Close()
if _, err := io.Copy(t, s); err != nil {
sse.SendServerError(err)
return
}
}
}
// --------------------------------------------------------------------------------
// --- [ Upload Metadata ] --------------------------------------------------------
// --------------------------------------------------------------------------------
{
// --- Begin Transaction ---
tx, err := tools.Database.Begin(ctx)
if err != nil {
sse.SendServerError(err)
return
}
defer tx.Rollback(ctx)
// --- Insert Row ---
if _, err = tx.Exec(ctx,
`INSERT INTO gifuu.upload (
id,
upload_address_hash,
upload_token_hash,
flag_sticker,
flag_audio,
encode_fps,
encode_width,
encode_height,
meta_rating,
meta_title
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
TempID,
ClientAddress,
tools.RequestHash(ClientToken),
MediaSticker,
ProbeAudio != nil,
MediaFramerate,
MediaWidth,
MediaHeight,
MediaRating,
Body.Title,
); err != nil {
sse.SendServerError(err)
return
}
// --- Upsert Tags ---
if _, err := tx.Exec(ctx,
`WITH upserted_tags AS (
INSERT INTO gifuu.tag (label)
SELECT unnest($1::text[])
ON CONFLICT (label) DO UPDATE SET label = EXCLUDED.label
RETURNING id
)
INSERT INTO gifuu.upload_tag (gif_id, tag_id)
SELECT $2, id FROM upserted_tags`,
Body.Tags,
TempID,
); err != nil {
sse.SendServerError(err)
return
}
// --- Commit Transaction ---
if err := tx.Commit(ctx); err != nil {
sse.SendServerError(err)
return
}
}
// --------------------------------------------------------------------------------
// --- [ Return Results ] ---------------------------------------------------------
// --------------------------------------------------------------------------------
TempSuccess = true
sse.SendJSON("finish", map[string]any{
"id": strconv.FormatInt(TempID, 10),
"edit_token": ClientToken,
})
}