merge.go (view raw)
1package main
2
3import (
4 "errors"
5 "fmt"
6 "io"
7 "net/http"
8 "os"
9 "os/exec"
10 "strconv"
11)
12
13const (
14 outputPath = "output.mp4"
15 audioPath = "temp_audio.opus"
16 videoPath = "temp_video.mp4"
17)
18
19var sem = make(chan struct{}, 1) // Semaforo con buffer 1
20
21func saveUploadedFile(file io.Reader, path string) error {
22 out, err := os.Create(path)
23 if err != nil {
24 return err
25 }
26 defer out.Close()
27
28 _, err = io.Copy(out, file)
29 return err
30}
31
32// Funzione helper per restituire un errore di richiesta non valida
33func badRequest(w http.ResponseWriter, reason string) error {
34 http.Error(w, reason, http.StatusBadRequest)
35 return errors.New(reason)
36}
37
38// Funzione per leggere e validare i dati del form e salvare i file temporaneamente
39func parseAndValidateForm(w http.ResponseWriter, r *http.Request) (skip, fade, delay, duration float64, err error) {
40 if r.Method != http.MethodPost {
41 err = badRequest(w, "Metodo non supportato")
42 return
43 }
44
45 // Parsing del form
46 if err = r.ParseMultipartForm(10 << 20); err != nil {
47 err = badRequest(w, "Errore nel form")
48 return
49 }
50
51 // Recupera i file audio e video
52 audioFile, audioHeader, err := r.FormFile("audioFile")
53 if err != nil {
54 err = badRequest(w, "File audio mancante")
55 return
56 }
57 defer audioFile.Close()
58
59 videoFile, videoHeader, err := r.FormFile("videoFile")
60 if err != nil {
61 err = badRequest(w, "File video mancante")
62 return
63 }
64 defer videoFile.Close()
65
66 // Controllo delle dimensioni dei file
67 const maxFileSize = 100 << 20 // 100 MB
68 if audioHeader.Size > maxFileSize {
69 err = badRequest(w, "File audio troppo grande")
70 return
71 }
72 if videoHeader.Size > maxFileSize {
73 err = badRequest(w, "File video troppo grande")
74 return
75 }
76
77 // Recupera e valida i parametri dal form
78 if skip, err = strconv.ParseFloat(r.FormValue("skip"), 64); err != nil || skip < 0 {
79 err = badRequest(w, "Parametro skip non valido")
80 return
81 }
82
83 if fade, err = strconv.ParseFloat(r.FormValue("fade"), 64); err != nil || fade < 0 {
84 err = badRequest(w, "Parametro fade non valido")
85 return
86 }
87
88 if delay, err = strconv.ParseFloat(r.FormValue("delay"), 64); err != nil || delay < 0 {
89 err = badRequest(w, "Parametro delay non valido")
90 return
91 }
92
93 if duration, err = strconv.ParseFloat(r.FormValue("duration"), 64); err != nil || duration <= 0 {
94 err = badRequest(w, "Parametro duration non valido")
95 return
96 }
97
98 // Salva i file temporaneamente
99 if err = saveUploadedFile(audioFile, audioPath); err != nil {
100 http.Error(w, "Errore nel salvataggio del file audio", http.StatusInternalServerError)
101 return
102 }
103 if err = saveUploadedFile(videoFile, videoPath); err != nil {
104 http.Error(w, "Errore nel salvataggio del file video", http.StatusInternalServerError)
105 return
106 }
107
108 return
109}
110
111// Funzione per gestire il processamento multimediale
112func processMedia(w http.ResponseWriter, skip, fade, delay, duration float64) error {
113 // Conversione delay in millisecondi
114 delayMs := int(delay * 1000)
115 fadeOutStart := duration - fade
116
117 // Costruzione del filtro audio
118 audioFilter := fmt.Sprintf("afade=t=out:st=%.2f:d=%.2f", fadeOutStart, fade)
119 if skip >= fade {
120 audioFilter = fmt.Sprintf("afade=t=in:ss=0:d=%.2f,%s", fade, audioFilter)
121 }
122
123 // Costruzione del comando ffmpeg
124 ffmpegCommand := []string{
125 "-i", videoPath,
126 "-i", audioPath,
127 "-filter_complex",
128 fmt.Sprintf("[1:a]atrim=start=%.2f:end=%.2f,asetpts=PTS-STARTPTS,%s,adelay=%d|%d[song];[0:a][song]amix=inputs=2:duration=first[audio_mix];[0:v]copy[v]",
129 skip, skip+duration, audioFilter, delayMs, delayMs),
130 "-map", "[v]", "-map", "[audio_mix]",
131 "-c:v", "libx264",
132 "-c:a", "aac",
133 "-shortest", outputPath,
134 "-y",
135 }
136
137 // Esecuzione del comando ffmpeg
138 cmd := exec.Command("ffmpeg", ffmpegCommand...)
139 cmd.Stderr = os.Stderr
140 cmd.Stdout = os.Stdout
141
142 if err := cmd.Run(); err != nil {
143 return fmt.Errorf("errore durante l'esecuzione di ffmpeg: %v", err)
144 }
145
146 // Impostazioni per scaricare il file di output
147 w.Header().Set("Content-Disposition", "attachment; filename=output.mp4")
148 w.Header().Set("Content-Type", "video/mp4")
149
150 // Pulizia dei file temporanei
151 os.Remove(audioPath)
152 os.Remove(videoPath)
153
154 return nil
155}
156
157// Handler principale
158func processHandler(w http.ResponseWriter, r *http.Request) {
159 sem <- struct{}{}
160 defer func() { <-sem }() // Rilascia il semaforo alla fine
161
162 skip, fade, delay, duration, err := parseAndValidateForm(w, r)
163 if err != nil {
164 return // Gli errori vengono giĆ gestiti in parseAndValidateForm
165 }
166
167 if err := processMedia(w, skip, fade, delay, duration); err != nil {
168 http.Error(w, "Errore durante il processamento multimediale", http.StatusInternalServerError)
169 return
170 }
171
172 // Restituisce il file output.mp4 in risposta
173 outputFile, err := os.Open(outputPath)
174 if err != nil {
175 return
176 }
177 defer outputFile.Close()
178
179 if _, err = io.Copy(w, outputFile); err != nil {
180 return
181 }
182
183 os.Remove(outputPath)
184}