tagliatelle

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

include-admin-thumbnails.go (6460B)


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