tagliatelle

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

include-admin-thumbnails.go (5929B)


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