tagliatelle

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

include-admin-thumbnails.go (6296B)


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