up
This commit is contained in:
parent
12e9c11536
commit
6aebad2db7
@ -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("/stream/{partID:[0-9]+}", renders.Stream(bd)).Methods("GET")
|
||||||
r.HandleFunc("/media/{partID:[0-9]+}", renders.MediaDetail(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]+}/{file}", renders.HLSStream(bd)).Methods("GET")
|
||||||
|
r.HandleFunc("/hls/{partID:[0-9]+}/", renders.HLSStream(bd)).Methods("GET")
|
||||||
//API Scan folder
|
//API Scan folder
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -988,10 +988,11 @@ type mediaDetailView struct {
|
|||||||
HLSURL string // ajouté
|
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 {
|
func MediaDetail(db *gorm.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// 1) Extraire le partID
|
|
||||||
partID, _ := strconv.ParseInt(mux.Vars(r)["partID"], 10, 64)
|
partID, _ := strconv.ParseInt(mux.Vars(r)["partID"], 10, 64)
|
||||||
|
|
||||||
var view mediaDetailView
|
var view mediaDetailView
|
||||||
@ -1016,15 +1017,16 @@ func MediaDetail(db *gorm.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// formater la durée
|
// formatage durée
|
||||||
m := item.Duration / 60
|
m := item.Duration / 60
|
||||||
s := item.Duration % 60
|
s := item.Duration % 60
|
||||||
|
|
||||||
view = mediaDetailView{
|
view = mediaDetailView{
|
||||||
Title: item.Title,
|
Title: item.Title,
|
||||||
Summary: item.Summary,
|
Summary: item.Summary,
|
||||||
DurationFmt: strconv.FormatInt(m, 10) + ":" + fmt.Sprintf("%02d", s),
|
DurationFmt: fmt.Sprintf("%d:%02d", m, s),
|
||||||
ThumbURL: item.UserThumbURL,
|
ThumbURL: item.UserThumbURL,
|
||||||
HLSURL: fmt.Sprintf("/hls/%d/index.m3u8", partID),
|
HLSURL: fmt.Sprintf("/hls/%d/index.m3u8", item.MediaPartID),
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -1035,39 +1037,37 @@ func MediaDetail(db *gorm.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// titre
|
// base name et thumbnail
|
||||||
title := filepath.Base(path)
|
title := filepath.Base(path)
|
||||||
|
|
||||||
// génère un thumbnail si besoin
|
|
||||||
ext := filepath.Ext(path)
|
ext := filepath.Ext(path)
|
||||||
base := strings.TrimSuffix(filepath.Base(path), ext)
|
base := strings.TrimSuffix(title, ext)
|
||||||
thumbDir := filepath.Join("static", "thumbs")
|
thumbDir := filepath.Join("static", "thumbs")
|
||||||
os.MkdirAll(thumbDir, 0755)
|
os.MkdirAll(thumbDir, 0755)
|
||||||
thumbPath := filepath.Join(thumbDir, base+".jpg")
|
thumbPath := filepath.Join(thumbDir, base+".jpg")
|
||||||
if _, err := os.Stat(thumbPath); os.IsNotExist(err) {
|
if _, err := os.Stat(thumbPath); os.IsNotExist(err) {
|
||||||
// screenshot au 5s
|
// capture au 5s
|
||||||
exec.CommandContext(r.Context(),
|
exec.CommandContext(context.Background(),
|
||||||
"ffmpeg", "-ss", "5", "-i", path, "-frames:v", "1", thumbPath,
|
"ffmpeg", "-ss", "5", "-i", path, "-frames:v", "1", thumbPath,
|
||||||
).Run()
|
).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
view = mediaDetailView{
|
view = mediaDetailView{
|
||||||
Title: title,
|
Title: title,
|
||||||
Summary: "", // pas de résumé en FS-only
|
Summary: "",
|
||||||
DurationFmt: "", // on ne probe pas ici
|
DurationFmt: "",
|
||||||
ThumbURL: "/static/thumbs/" + base + ".jpg",
|
ThumbURL: "/static/thumbs/" + base + ".jpg",
|
||||||
// on passe le path en query pour le streaming
|
// **ici** on passe le path en query pour que HLSStream sache où chercher
|
||||||
HLSURL: "/hls/0/index.m3u8?path=" + url.QueryEscape(path),
|
HLSURL: fmt.Sprintf("/hls/0/index.m3u8?path=%s", url.QueryEscape(path)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Render partial dans #content
|
|
||||||
renderPartial(w, "media_detail", map[string]interface{}{
|
renderPartial(w, "media_detail", map[string]interface{}{
|
||||||
"item": view,
|
"item": view,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Stream : transcode à la volée en MP4 progressif et pipe directement dans la réponse
|
// Stream : transcode à la volée en MP4 progressif et pipe directement dans la réponse
|
||||||
func Stream(db *gorm.DB) http.HandlerFunc {
|
func Stream(db *gorm.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
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)
|
// 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 {
|
func HLSStream(db *gorm.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
// 1) identifier le partID et le dossier temporaire
|
||||||
partID, _ := strconv.ParseInt(vars["partID"], 10, 64)
|
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
|
var filePath string
|
||||||
|
|
||||||
if partID > 0 {
|
if partID > 0 {
|
||||||
// cas BDD
|
// cas BDD
|
||||||
var part models.MediaPart
|
var part models.MediaPart
|
||||||
@ -1114,22 +1119,22 @@ func HLSStream(db *gorm.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
filePath = part.File
|
filePath = part.File
|
||||||
|
needGen = true
|
||||||
} else {
|
} else {
|
||||||
// cas FS-only
|
// 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")
|
filePath = r.URL.Query().Get("path")
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
http.Error(w, "Média introuvable", http.StatusNotFound)
|
http.Error(w, "Média introuvable", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
needGen = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Préparer le dossier de cache HLS
|
// 3) (Re)générer le HLS si besoin
|
||||||
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("hls_%d", partID))
|
if needGen {
|
||||||
os.MkdirAll(tmpDir, 0755)
|
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) {
|
|
||||||
cmd := exec.CommandContext(r.Context(),
|
cmd := exec.CommandContext(r.Context(),
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
"-i", filePath,
|
"-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)
|
prefix := fmt.Sprintf("/hls/%d/", partID)
|
||||||
http.StripPrefix(prefix,
|
http.StripPrefix(prefix,
|
||||||
http.FileServer(http.Dir(tmpDir)),
|
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{}) {
|
func renderPartial(w http.ResponseWriter, templ string, data map[string]interface{}) {
|
||||||
// Exécute directement le define `<templ>.pages.tmpl`
|
// Exécute directement le define `<templ>.pages.tmpl`
|
||||||
if err := templates.ExecuteTemplate(w, templ+".pages.tmpl", data); err != nil {
|
if err := templates.ExecuteTemplate(w, templ+".pages.tmpl", data); err != nil {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user