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 }