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 }