From 6aebad2db78feacd534b42e3c6c57d51aea12bee Mon Sep 17 00:00:00 2001 From: cangui Date: Sat, 21 Jun 2025 19:55:53 +0200 Subject: [PATCH] up --- internal/route/main.go | 2 +- renders/renders.go | 74 +++++++++++++++++++++++------------------- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/internal/route/main.go b/internal/route/main.go index 25ed7be..34fd70b 100644 --- a/internal/route/main.go +++ b/internal/route/main.go @@ -229,7 +229,7 @@ r.HandleFunc("/api/paths/{id:[0-9]+}/media", renders.PathMedia(bd)).Methods("GET r.HandleFunc("/stream/{partID:[0-9]+}", renders.Stream(bd)).Methods("GET") r.HandleFunc("/media/{partID:[0-9]+}", renders.MediaDetail(bd)).Methods("GET") r.HandleFunc("/hls/{partID:[0-9]+}/{file}", renders.HLSStream(bd)).Methods("GET") - +r.HandleFunc("/hls/{partID:[0-9]+}/", renders.HLSStream(bd)).Methods("GET") //API Scan folder } diff --git a/renders/renders.go b/renders/renders.go index 5947eb9..bb91f07 100644 --- a/renders/renders.go +++ b/renders/renders.go @@ -988,10 +988,11 @@ type mediaDetailView struct { HLSURL string // ajouté } -// MediaDetail affiche la page détail + player + + +// MediaDetail renvoie la partial HTML du détail d’un média func MediaDetail(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // 1) Extraire le partID partID, _ := strconv.ParseInt(mux.Vars(r)["partID"], 10, 64) var view mediaDetailView @@ -1000,8 +1001,8 @@ func MediaDetail(db *gorm.DB) http.HandlerFunc { // --- CAS BDD --- var item struct { models.MetadataItem - MediaPartID int64 - File string + MediaPartID int64 + File string UserThumbURL string } db.Table("metadata_items"). @@ -1016,15 +1017,16 @@ func MediaDetail(db *gorm.DB) http.HandlerFunc { return } - // formater la durée + // formatage durée m := item.Duration / 60 s := item.Duration % 60 + view = mediaDetailView{ Title: item.Title, Summary: item.Summary, - DurationFmt: strconv.FormatInt(m, 10) + ":" + fmt.Sprintf("%02d", s), + DurationFmt: fmt.Sprintf("%d:%02d", m, s), ThumbURL: item.UserThumbURL, - HLSURL: fmt.Sprintf("/hls/%d/index.m3u8", partID), + HLSURL: fmt.Sprintf("/hls/%d/index.m3u8", item.MediaPartID), } } else { @@ -1035,39 +1037,37 @@ func MediaDetail(db *gorm.DB) http.HandlerFunc { return } - // titre + // base name et thumbnail title := filepath.Base(path) - - // génère un thumbnail si besoin ext := filepath.Ext(path) - base := strings.TrimSuffix(filepath.Base(path), ext) + base := strings.TrimSuffix(title, ext) thumbDir := filepath.Join("static", "thumbs") os.MkdirAll(thumbDir, 0755) thumbPath := filepath.Join(thumbDir, base+".jpg") if _, err := os.Stat(thumbPath); os.IsNotExist(err) { - // screenshot au 5s - exec.CommandContext(r.Context(), + // capture au 5s + exec.CommandContext(context.Background(), "ffmpeg", "-ss", "5", "-i", path, "-frames:v", "1", thumbPath, ).Run() } view = mediaDetailView{ Title: title, - Summary: "", // pas de résumé en FS-only - DurationFmt: "", // on ne probe pas ici + Summary: "", + DurationFmt: "", ThumbURL: "/static/thumbs/" + base + ".jpg", - // on passe le path en query pour le streaming - HLSURL: "/hls/0/index.m3u8?path=" + url.QueryEscape(path), + // **ici** on passe le path en query pour que HLSStream sache où chercher + HLSURL: fmt.Sprintf("/hls/0/index.m3u8?path=%s", url.QueryEscape(path)), } } - // 3) Render partial dans #content renderPartial(w, "media_detail", map[string]interface{}{ "item": view, }) } } + // Stream : transcode à la volée en MP4 progressif et pipe directement dans la réponse func Stream(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -1099,13 +1099,18 @@ func Stream(db *gorm.DB) http.HandlerFunc { } // renders/media.go (ajoutez cette fonction) +// rend le HLS pour BDD (partID>0) et FS-only (partID==0) func HLSStream(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - partID, _ := strconv.ParseInt(vars["partID"], 10, 64) + // 1) identifier le partID et le dossier temporaire + partID, _ := strconv.ParseInt(mux.Vars(r)["partID"], 10, 64) + tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("hls_%d", partID)) + playlist := filepath.Join(tmpDir, "index.m3u8") - // Déterminer le fichier source + // 2) déterminer s'il faut (re)générer + needGen := false var filePath string + if partID > 0 { // cas BDD var part models.MediaPart @@ -1114,22 +1119,22 @@ func HLSStream(db *gorm.DB) http.HandlerFunc { return } filePath = part.File + needGen = true } else { - // cas FS-only - filePath = r.URL.Query().Get("path") - if filePath == "" { - http.Error(w, "Média introuvable", http.StatusNotFound) - return + // cas FS-only : on génère seulement si playlist manquante + if _, err := os.Stat(playlist); os.IsNotExist(err) { + filePath = r.URL.Query().Get("path") + if filePath == "" { + http.Error(w, "Média introuvable", http.StatusNotFound) + return + } + needGen = true } } - // Préparer le dossier de cache HLS - tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("hls_%d", partID)) - os.MkdirAll(tmpDir, 0755) - playlist := filepath.Join(tmpDir, "index.m3u8") - - // (Re)générer si nécessaire - if _, err := os.Stat(playlist); os.IsNotExist(err) { + // 3) (Re)générer le HLS si besoin + if needGen { + os.MkdirAll(tmpDir, 0755) cmd := exec.CommandContext(r.Context(), "ffmpeg", "-i", filePath, @@ -1147,7 +1152,7 @@ func HLSStream(db *gorm.DB) http.HandlerFunc { } } - // Servir le dossier HLS + // 4) servir **tout** tmpDir sous /hls/{partID}/… prefix := fmt.Sprintf("/hls/%d/", partID) http.StripPrefix(prefix, http.FileServer(http.Dir(tmpDir)), @@ -1159,6 +1164,7 @@ func HLSStream(db *gorm.DB) http.HandlerFunc { + func renderPartial(w http.ResponseWriter, templ string, data map[string]interface{}) { // Exécute directement le define `.pages.tmpl` if err := templates.ExecuteTemplate(w, templ+".pages.tmpl", data); err != nil {