tagliatelle

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

include-admin-thumbnails.go (6167B)


      1 package main
      2 
      3 import (
      4     "fmt"
      5     "log"
      6     "net/http"
      7     "net/url"
      8     "os"
      9     "os/exec"
     10     "path/filepath"
     11     "strings"
     12 )
     13 
     14 func generateThumbnailAtTime(videoPath, uploadDir, filename, timestamp string) error {
     15 	thumbDir := filepath.Join(uploadDir, "thumbnails")
     16 	if err := os.MkdirAll(thumbDir, 0755); err != nil {
     17 		return fmt.Errorf("failed to create thumbnails directory: %v", err)
     18 	}
     19 
     20 	thumbPath := filepath.Join(thumbDir, filename+".jpg")
     21 
     22 	cmd := exec.Command("ffmpeg", "-y", "-ss", timestamp, "-i", videoPath, "-vframes", "1", "-vf", "scale=400:-1", thumbPath)
     23 	cmd.Stdout = os.Stdout
     24 	cmd.Stderr = os.Stderr
     25 
     26 	if err := cmd.Run(); err != nil {
     27 		return fmt.Errorf("failed to generate thumbnail at %s: %v", timestamp, err)
     28 	}
     29 
     30 	return nil
     31 }
     32 
     33 func getVideoFiles() ([]VideoFile, error) {
     34 	videoExts := []string{".mp4", ".webm", ".mov", ".avi", ".mkv", ".m4v"}
     35 
     36 	rows, err := db.Query(`SELECT id, filename, path FROM files ORDER BY id DESC`)
     37 	if err != nil {
     38 		return nil, err
     39 	}
     40 	defer rows.Close()
     41 
     42 	var videos []VideoFile
     43 	for rows.Next() {
     44 		var v VideoFile
     45 		if err := rows.Scan(&v.ID, &v.Filename, &v.Path); err != nil {
     46 			log.Printf("Warning: getVideoFiles: failed to scan row: %v", err)
     47 			continue
     48 		}
     49 
     50 		// Check if it's a video file
     51 		isVideo := false
     52 		ext := strings.ToLower(filepath.Ext(v.Filename))
     53 		for _, vidExt := range videoExts {
     54 			if ext == vidExt {
     55 				isVideo = true
     56 				break
     57 			}
     58 		}
     59 
     60 		if !isVideo {
     61 			continue
     62 		}
     63 
     64 		v.EscapedFilename = url.PathEscape(v.Filename)
     65 		thumbPath := filepath.Join(config.UploadDir, "thumbnails", v.Filename+".jpg")
     66 		v.ThumbnailPath = "/uploads/thumbnails/" + v.EscapedFilename + ".jpg"
     67 
     68 		if _, err := os.Stat(thumbPath); err == nil {
     69 			v.HasThumbnail = true
     70 		}
     71 
     72 		videos = append(videos, v)
     73 	}
     74 
     75 	return videos, nil
     76 }
     77 
     78 
     79 
     80 func thumbnailsHandler(w http.ResponseWriter, r *http.Request) {
     81 	allVideos, err := getVideoFiles()
     82 	if err != nil {
     83 		log.Printf("Error: thumbnailsHandler: failed to get video files: %v", err)
     84 		renderError(w, "Failed to get video files: "+err.Error(), http.StatusInternalServerError)
     85 		return
     86 	}
     87 
     88 	missing, err := getMissingThumbnailVideos()
     89 	if err != nil {
     90 		log.Printf("Error: thumbnailsHandler: failed to get missing thumbnail videos: %v", err)
     91 		renderError(w, "Failed to get video files: "+err.Error(), http.StatusInternalServerError)
     92 		return
     93 	}
     94 
     95 	pageData := buildPageData("Thumbnail Management", struct {
     96 		AllVideos         []VideoFile
     97 		MissingThumbnails []VideoFile
     98 		Error             string
     99 		Success           string
    100 	}{
    101 		AllVideos:         allVideos,
    102 		MissingThumbnails: missing,
    103 		Error:             r.URL.Query().Get("error"),
    104 		Success:           r.URL.Query().Get("success"),
    105 	})
    106 
    107 	renderTemplate(w, "thumbnails.html", pageData)
    108 }
    109 
    110 func generateThumbnailHandler(w http.ResponseWriter, r *http.Request) {
    111 	if r.Method != http.MethodPost {
    112 		http.Redirect(w, r, "/admin", http.StatusSeeOther)
    113 		return
    114 	}
    115 
    116 	action := r.FormValue("action")
    117 	redirectTo := r.FormValue("redirect")
    118 	if redirectTo == "" {
    119 		redirectTo = "thumbnails"
    120 	}
    121 
    122 	redirectBase := "/" + redirectTo
    123 
    124 	switch action {
    125 	case "generate_all":
    126 		missing, err := getMissingThumbnailVideos()
    127 		if err != nil {
    128 			log.Printf("Error: generateThumbnailHandler: failed to get missing thumbnails: %v", err)
    129 			http.Redirect(w, r, redirectBase+"?error="+url.QueryEscape("Failed to get videos: "+err.Error()), http.StatusSeeOther)
    130 			return
    131 		}
    132 
    133 		successCount := 0
    134 		var errors []string
    135 
    136 		for _, v := range missing {
    137 			if err := generateThumbnail(v.Path, config.UploadDir, v.Filename); err != nil {
    138 				log.Printf("Error: generateThumbnailHandler: failed to generate thumbnail for %s: %v", v.Filename, err)
    139 				errors = append(errors, fmt.Sprintf("%s: %v", v.Filename, err))
    140 			} else {
    141 				successCount++
    142 			}
    143 		}
    144 
    145 		if len(errors) > 0 {
    146 			http.Redirect(w, r, redirectBase+"?success="+url.QueryEscape(fmt.Sprintf("Generated %d thumbnails", successCount))+"&error="+url.QueryEscape(fmt.Sprintf("Failed: %s", strings.Join(errors, "; "))), http.StatusSeeOther)
    147 		} else {
    148 			http.Redirect(w, r, redirectBase+"?success="+url.QueryEscape(fmt.Sprintf("Successfully generated %d thumbnails", successCount)), http.StatusSeeOther)
    149 		}
    150 
    151 	case "generate_single":
    152 		fileID := r.FormValue("file_id")
    153 		timestamp := strings.TrimSpace(r.FormValue("timestamp"))
    154 
    155 		if timestamp == "" {
    156 			timestamp = "00:00:05"
    157 		}
    158 
    159 		var filename, path string
    160 		err := db.QueryRow("SELECT filename, path FROM files WHERE id=?", fileID).Scan(&filename, &path)
    161 		if err != nil {
    162 			log.Printf("Error: generateThumbnailHandler: file not found for id=%s: %v", fileID, err)
    163 			http.Redirect(w, r, redirectBase+"?error="+url.QueryEscape("File not found"), http.StatusSeeOther)
    164 			return
    165 		}
    166 
    167 		if err = generateThumbnailAtTime(path, config.UploadDir, filename, timestamp); err != nil {
    168 			log.Printf("Error: generateThumbnailHandler: failed to generate thumbnail for file id=%s at %s: %v", fileID, timestamp, err)
    169 			http.Redirect(w, r, redirectBase+"?error="+url.QueryEscape("Failed to generate thumbnail: "+err.Error()), http.StatusSeeOther)
    170 			return
    171 		}
    172 
    173 		if redirectTo == "admin" {
    174 			http.Redirect(w, r, "/admin?success="+url.QueryEscape(fmt.Sprintf("Thumbnail generated for file %s at %s", fileID, timestamp)), http.StatusSeeOther)
    175 		} else {
    176 			http.Redirect(w, r, fmt.Sprintf("/file/%s?success=%s", fileID, url.QueryEscape(fmt.Sprintf("Thumbnail generated at %s", timestamp))), http.StatusSeeOther)
    177 		}
    178 
    179 	default:
    180 		http.Redirect(w, r, redirectBase, http.StatusSeeOther)
    181 	}
    182 }
    183 
    184 func generateThumbnail(videoPath, uploadDir, filename string) error {
    185 	if err := generateThumbnailAtTime(videoPath, uploadDir, filename, "00:00:05"); err != nil {
    186 		log.Printf("Warning: generateThumbnail: seek to 5s failed for %s, retrying from start: %v", filename, err)
    187 		return generateThumbnailAtTime(videoPath, uploadDir, filename, "00:00:00")
    188 	}
    189 
    190 	return nil
    191 }
    192 
    193 func getMissingThumbnailVideos() ([]VideoFile, error) {
    194 	allVideos, err := getVideoFiles()
    195 	if err != nil {
    196 		return nil, err
    197 	}
    198 
    199 	var missing []VideoFile
    200 	for _, v := range allVideos {
    201 		if !v.HasThumbnail {
    202 			missing = append(missing, v)
    203 		}
    204 	}
    205 
    206 	return missing, nil
    207 }