feat: enhance update feedback with loading and success messages in forms and video processing
This commit is contained in:
parent
c31a68ef6d
commit
f1bd40afce
4 changed files with 77 additions and 26 deletions
|
|
@ -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")
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
);
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue