|
1 | 1 | package player |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bufio" |
4 | 5 | "encoding/json" |
5 | 6 | "fmt" |
6 | 7 | "io" |
@@ -153,13 +154,81 @@ func DownloadVideo(url, destPath string, numThreads int, m *model) error { |
153 | 154 | return nil |
154 | 155 | } |
155 | 156 |
|
156 | | -// downloadWithYtDlp downloads a video using yt-dlp. |
157 | | -func downloadWithYtDlp(url, path string) error { |
158 | | - cmd := exec.Command("yt-dlp", "--no-progress", "-f", "best", "-o", path, url) |
159 | | - if output, err := cmd.CombinedOutput(); err != nil { |
160 | | - return fmt.Errorf("yt-dlp error: %v\n%s", err, string(output)) |
| 157 | +// downloadWithYtDlp downloads a video using yt-dlp and updates the progress model if provided. |
| 158 | +func downloadWithYtDlp(url, path string, m *model) error { |
| 159 | + // Build yt-dlp command with newline progress and no colors |
| 160 | + args := []string{"--newline", "--no-color", "-f", "best", "-o", path, url} |
| 161 | + cmd := exec.Command("yt-dlp", args...) |
| 162 | + |
| 163 | + stderr, err := cmd.StderrPipe() |
| 164 | + if err != nil { |
| 165 | + return fmt.Errorf("yt-dlp stderr pipe: %w", err) |
161 | 166 | } |
162 | | - return nil |
| 167 | + stdout, err := cmd.StdoutPipe() |
| 168 | + if err != nil { |
| 169 | + return fmt.Errorf("yt-dlp stdout pipe: %w", err) |
| 170 | + } |
| 171 | + |
| 172 | + if err := cmd.Start(); err != nil { |
| 173 | + // Fallback to original behavior to not break flow |
| 174 | + out, e := exec.Command("yt-dlp", "--no-progress", "-f", "best", "-o", path, url).CombinedOutput() |
| 175 | + if e != nil { |
| 176 | + return fmt.Errorf("yt-dlp error: %v\n%s", e, string(out)) |
| 177 | + } |
| 178 | + return nil |
| 179 | + } |
| 180 | + |
| 181 | + // Estimate per-episode total for progress accounting |
| 182 | + var epTotal int64 |
| 183 | + var lastBytes int64 |
| 184 | + if m != nil { |
| 185 | + client := &http.Client{Transport: api.SafeTransport(10 * time.Second)} |
| 186 | + if sz, e := getContentLength(url, client); e == nil && sz > 0 { |
| 187 | + epTotal = sz |
| 188 | + } else { |
| 189 | + // Fallback estimate for HLS |
| 190 | + epTotal = 500 * 1024 * 1024 |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + reader := bufio.NewScanner(io.MultiReader(stdout, stderr)) |
| 195 | + // Increase buffer in case yt-dlp outputs long lines |
| 196 | + buf := make([]byte, 0, 1024*64) |
| 197 | + reader.Buffer(buf, 1024*1024) |
| 198 | + percentRe := regexp.MustCompile(`(?i)(\d{1,3}(?:\.\d+)?)\s*%`) |
| 199 | + for reader.Scan() { |
| 200 | + line := reader.Text() |
| 201 | + // Parse percent and update shared progress |
| 202 | + if m != nil && epTotal > 0 { |
| 203 | + if pm := percentRe.FindStringSubmatch(line); len(pm) > 1 { |
| 204 | + pStr := pm[1] |
| 205 | + pVal, _ := strconv.ParseFloat(pStr, 64) |
| 206 | + if pVal < 0 { |
| 207 | + pVal = 0 |
| 208 | + } |
| 209 | + if pVal > 100 { |
| 210 | + pVal = 100 |
| 211 | + } |
| 212 | + current := int64(float64(epTotal) * (pVal / 100.0)) |
| 213 | + delta := current - lastBytes |
| 214 | + if delta > 0 { |
| 215 | + m.mu.Lock() |
| 216 | + m.received += delta |
| 217 | + m.mu.Unlock() |
| 218 | + lastBytes = current |
| 219 | + } |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + // Wait for command completion |
| 224 | + err = cmd.Wait() |
| 225 | + if m != nil && epTotal > 0 && lastBytes < epTotal { |
| 226 | + // Ensure completion accounts for full episode size |
| 227 | + m.mu.Lock() |
| 228 | + m.received += (epTotal - lastBytes) |
| 229 | + m.mu.Unlock() |
| 230 | + } |
| 231 | + return err |
163 | 232 | } |
164 | 233 |
|
165 | 234 | // ExtractVideoSources returns the available video sources for an episode. |
@@ -437,9 +506,9 @@ func HandleBatchDownload(episodes []models.Episode, animeURL string) error { |
437 | 506 | } |
438 | 507 | // Use yt-dlp for HLS/DASH playlists and hosters that require it |
439 | 508 | if strings.Contains(videoURL, ".m3u8") || strings.Contains(videoURL, ".mpd") || strings.Contains(videoURL, "repackager.wixmp.com") { |
440 | | - err = downloadWithYtDlp(videoURL, episodePath) |
| 509 | + err = downloadWithYtDlp(videoURL, episodePath, m) |
441 | 510 | } else if strings.Contains(videoURL, "blogger.com") { |
442 | | - err = downloadWithYtDlp(videoURL, episodePath) |
| 511 | + err = downloadWithYtDlp(videoURL, episodePath, m) |
443 | 512 | } else { |
444 | 513 | err = DownloadVideo(videoURL, episodePath, 4, m) |
445 | 514 | } |
|
0 commit comments