diff --git a/apps/backend/server.go b/apps/backend/server.go index 3da1dc9..c3638fe 100644 --- a/apps/backend/server.go +++ b/apps/backend/server.go @@ -167,6 +167,7 @@ func (s *Server) SetupRoutes() *echo.Echo { return c.String(http.StatusInternalServerError, err.Error()) } */ + log.Println("Update completed") return c.String(http.StatusOK, "Updated") }) e.GET("/update-cod/:cod", func(c echo.Context) error { @@ -207,6 +208,7 @@ func (s *Server) SetupRoutes() *echo.Echo { return c.String(http.StatusInternalServerError, err.Error()) } */ + log.Printf("Update %s completed", cod) return c.String(http.StatusOK, "Updated") }) diff --git a/apps/backend/video.go b/apps/backend/video.go index 942a6da..5e60003 100644 --- a/apps/backend/video.go +++ b/apps/backend/video.go @@ -197,42 +197,65 @@ func VideoConversion(input string) (string, string, error) { ext := filepath.Ext(basename) nameWithoutExt := basename[0 : len(basename)-len(ext)] - tempVideo := filepath.Join(outputdir, nameWithoutExt+"_temp.mp4") + // Final destinations on the (potentially overlayfs) volume videoOutput := filepath.Join(outputdir, nameWithoutExt+".mp4") thumbOutput := filepath.Join(outputdir, "thumbnail-"+nameWithoutExt+".webp") - // 1. Transcode video to H.264/AAC MP4 (web-optimized) - //log.Println("Transcoding video:", input) - err := transcodeVideo(input, tempVideo) + // Use /tmp for all ffmpeg work: /tmp is tmpfs and supports random-access + // seeks that overlayfs does not, which is required for MP4 muxing and + // for the faststart moov-atom relocation. + tmpVideo := filepath.Join(os.TempDir(), nameWithoutExt+"_converted.mp4") + tmpThumb := filepath.Join(os.TempDir(), "thumbnail-"+nameWithoutExt+".webp") + defer os.Remove(tmpVideo) + defer os.Remove(tmpThumb) + + // 1. Transcode video to H.264/AAC MP4 (web-optimized) into /tmp + err := transcodeVideo(input, tmpVideo) if err != nil { return "", "", fmt.Errorf("failed to transcode video: %w", err) } - // 2. Extract thumbnail from video at 1 second - //log.Println("Extracting thumbnail from:", input) - err = extractThumbnail(input, thumbOutput) + // 2. Extract thumbnail from original video into /tmp + err = extractThumbnail(input, tmpThumb) if err != nil { return "", "", fmt.Errorf("failed to extract thumbnail: %w", err) } + + // 3. Move completed files from /tmp to the videos volume os.Remove(input) - err = os.Rename(tempVideo, videoOutput) - if err != nil { - return "", "", fmt.Errorf("failed to rename converted video: %w", err) + if err = copyFile(tmpVideo, videoOutput); err != nil { + return "", "", fmt.Errorf("failed to move converted video: %w", err) + } + if err = copyFile(tmpThumb, thumbOutput); err != nil { + return "", "", fmt.Errorf("failed to move thumbnail: %w", err) } return videoOutput, thumbOutput, nil } -// transcodeVideo converts video to web-optimized MP4 +// copyFile copies src to dst, creating dst if needed. +func copyFile(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + if _, err = io.Copy(out, in); err != nil { + return err + } + return out.Sync() +} + +// transcodeVideo converts video to web-optimized MP4. +// output must be on a filesystem that supports random-access seeks (e.g. /tmp), +// because the MP4 muxer and -movflags +faststart both require re-opening the +// output file to relocate the moov atom — this fails on Docker overlayfs. func transcodeVideo(input, output string) error { - // FFmpeg command for web optimization: - // -c:v libx264: H.264 codec (widely supported) - // -preset fast: encoding speed/quality tradeoff - // -crf 23: quality (lower = better, 18-28 recommended) - // -c:a aac: AAC audio codec - // -b:a 128k: audio bitrate - // -movflags +faststart: enable streaming before full download - // -vf scale=1280:-2: scale to max width 1280, maintain aspect ratio cmd := exec.Command("ffmpeg", "-i", input, "-c:v", "libx264", @@ -241,7 +264,7 @@ func transcodeVideo(input, output string) error { "-c:a", "aac", "-b:a", "128k", "-movflags", "+faststart", - "-vf", "scale=min(1280\\,iw):-2", // Escape comma for cross-platform + "-vf", "scale=min(1280\\,iw):-2", "-y", output, ) diff --git a/apps/infoalloggi/src/forms/FormEditAnnuncio.tsx b/apps/infoalloggi/src/forms/FormEditAnnuncio.tsx index 429a616..b5bf076 100644 --- a/apps/infoalloggi/src/forms/FormEditAnnuncio.tsx +++ b/apps/infoalloggi/src/forms/FormEditAnnuncio.tsx @@ -141,15 +141,26 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => { }); const { mutateAsync: update } = api.revalidation.updateAnnuncio.useMutation({ + onMutate: () => { + toast.loading("Richiesta di aggiornamento inviata, attendere prego...", { + id: "update-annuncio", + }); + }, onSettled: async () => { await utils.annunci.getAnnuncio.invalidate({ cod: data.codice }); await utils.annunci.getAnnuncioById_rawImgUrls.invalidate({ id: data.id, }); - toast.success( - "L'aggiornamento dell'annuncio è stata richiesta con successo,\n potrebbero volerci alcuni minuti prima che le modifiche siano visibili.", + toast.success("Aggiornamento completato", { + id: "update-annuncio", + }); + }, + onError: async (err) => { + toast.error( + "Si è verificato un errore durante l'aggiornamento degli annunci: " + + (err instanceof Error ? err.message : "Errore sconosciuto"), { - duration: 5000, + id: "update-annuncio-all", }, ); }, diff --git a/apps/infoalloggi/src/pages/area-riservata/admin/annunci.tsx b/apps/infoalloggi/src/pages/area-riservata/admin/annunci.tsx index 9cffb5b..ee023c3 100644 --- a/apps/infoalloggi/src/pages/area-riservata/admin/annunci.tsx +++ b/apps/infoalloggi/src/pages/area-riservata/admin/annunci.tsx @@ -21,12 +21,27 @@ const Admin_Annunci: NextPageWithLayout = () => { const utils = api.useUtils(); const { mutateAsync: update } = api.revalidation.updateAllAnnunci.useMutation( { + onMutate: () => { + toast.loading( + "Richiesta di aggiornamento inviata, attendere prego...", + { + id: "update-annuncio-all", + }, + ); + }, + onSettled: async () => { await utils.annunci.getAnnunciList.invalidate(); - toast.success( - "La revalidazione dell'annuncio è stata richiesta con successo,\n potrebbero volerci alcuni minuti prima che le modifiche siano visibili.", + toast.success("Aggiornamento completato", { + id: "update-annuncio-all", + }); + }, + onError: async (err) => { + toast.error( + "Si è verificato un errore durante l'aggiornamento degli annunci: " + + (err instanceof Error ? err.message : "Errore sconosciuto"), { - duration: 5000, + id: "update-annuncio-all", }, ); },