replace progress percentage with actual file size

This commit is contained in:
2026-03-04 23:58:11 -05:00
parent 3cac63ba82
commit 481c8d9bb6
4 changed files with 93 additions and 68 deletions

View File

@@ -48,9 +48,8 @@ func startAsyncDownload(s *discordgo.Session, i *discordgo.InteractionCreate, ur
// Handle progress and results asynchronously // Handle progress and results asynchronously
go func() { go func() {
// First update the original ephemeral message with "Processing..."
_, err := s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{ _, err := s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
Content: ptr(fmt.Sprintf("Downloading video: %s", renderProgressBar(0))), Content: ptr("⏬ downloading: starting..."),
}) })
if err != nil { if err != nil {
log.Printf("Error updating interaction: %v", err) log.Printf("Error updating interaction: %v", err)
@@ -63,35 +62,28 @@ func startAsyncDownload(s *discordgo.Session, i *discordgo.InteractionCreate, ur
progressChan = nil progressChan = nil
continue continue
} }
var content string
if prog.Phase == "post-processing" { if prog.Phase == "post-processing" {
phaseEmoji := "⚙️" content = "⚙️ post-processing"
content := fmt.Sprintf("%s %s",
phaseEmoji,
prog.Phase)
_, err := s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
Content: ptr(content),
})
if err != nil {
log.Printf("Error updating progress: %v", err)
}
} else { } else {
phaseEmoji := "⏬" var progressStr string
content := fmt.Sprintf("%s %s: %s [eta: %s]\n📄 %s", if prog.DownloadedBytes > 0 {
phaseEmoji, progressStr = formatBytes(prog.DownloadedBytes) + " downloaded"
prog.Phase, } else {
renderProgressBar(prog.PercentFloat), progressStr = "starting..."
prog.ETA,
prog.Filename)
_, err := s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
Content: ptr(content),
})
if err != nil {
log.Printf("Error updating progress: %v", err)
} }
content = fmt.Sprintf("Downloading Video: %s", progressStr)
}
_, err := s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
Content: ptr(content),
})
if err != nil {
log.Printf("Error updating progress: %v", err)
} }
case result := <-resultChan: case result := <-resultChan:
// Handle completion
if result.Success { if result.Success {
_, err = s.FollowupMessageCreate(i.Interaction, false, &discordgo.WebhookParams{ _, err = s.FollowupMessageCreate(i.Interaction, false, &discordgo.WebhookParams{
Content: "📥 Video downloaded: " + result.URL, Content: "📥 Video downloaded: " + result.URL,

View File

@@ -1,19 +1,21 @@
package main package main
import ( import "fmt"
"fmt"
"strings"
)
// Helper function to create string pointer // Helper function to create string pointer
func ptr(s string) *string { func ptr(s string) *string {
return &s return &s
} }
const progressBarLength = 20 func formatBytes(b int) string {
switch {
func renderProgressBar(percent float64) string { case b >= 1<<30:
filled := int(float64(progressBarLength) * percent / 100) return fmt.Sprintf("%.1f GB", float64(b)/float64(1<<30))
bar := strings.Repeat("█", filled) + strings.Repeat(" ", progressBarLength-filled) case b >= 1<<20:
return fmt.Sprintf("[%s] %.0f%%", bar, percent) return fmt.Sprintf("%.1f MB", float64(b)/float64(1<<20))
case b >= 1<<10:
return fmt.Sprintf("%.1f KB", float64(b)/float64(1<<10))
default:
return fmt.Sprintf("%d B", b)
}
} }

View File

@@ -1,11 +1,5 @@
package main package main
import (
"time"
"github.com/lrstanley/go-ytdlp"
)
type DownloadOptions struct { type DownloadOptions struct {
EmbedThumbnail bool EmbedThumbnail bool
IncludeSubtitles bool IncludeSubtitles bool
@@ -35,12 +29,9 @@ type FormatOptions struct {
} }
type ProgressUpdate struct { type ProgressUpdate struct {
Status ytdlp.ProgressStatus Phase string
Percent string DownloadedBytes int
PercentFloat float64 Filename string
ETA time.Duration
Filename string
Phase string
} }
// InteractionState holds the state for a specific interaction // InteractionState holds the state for a specific interaction

View File

@@ -4,6 +4,8 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"os" "os"
"path/filepath"
"sync"
"time" "time"
"github.com/lrstanley/go-ytdlp" "github.com/lrstanley/go-ytdlp"
@@ -95,42 +97,80 @@ func GetFormats(url string) (*FormatOptions, error) {
return formatOpts, nil return formatOpts, nil
} }
func dirSize(path string) int {
var total int64
filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if err == nil && !info.IsDir() {
total += info.Size()
}
return nil
})
return int(total)
}
func DownloadVideo(out_dir, temp_dir, url string, opts DownloadOptions, progressChan chan<- ProgressUpdate) { func DownloadVideo(out_dir, temp_dir, url string, opts DownloadOptions, progressChan chan<- ProgressUpdate) {
defer close(progressChan) var mu sync.Mutex
currentPhase := "downloading"
currentFilename := ""
// Poll the temp directory for actual bytes-on-disk progress.
// The yt-dlp progress callback only tracks phase/filename since
// DownloadedBytes from the callback is unreliable for DASH streams.
var wg sync.WaitGroup
wg.Add(1)
stopPoll := make(chan struct{})
go func() {
defer wg.Done()
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-stopPoll:
return
case <-ticker.C:
mu.Lock()
phase := currentPhase
filename := currentFilename
mu.Unlock()
progressChan <- ProgressUpdate{
Phase: phase,
DownloadedBytes: dirSize(temp_dir),
Filename: filename,
}
}
}
}()
defer func() {
close(stopPoll)
wg.Wait()
close(progressChan)
}()
homePath := "home:" + out_dir homePath := "home:" + out_dir
tempPath := "temp:" + temp_dir tempPath := "temp:" + temp_dir
var lastPhase string
dl := ytdlp.New(). dl := ytdlp.New().
SetExecutable(ytdlpBinary). SetExecutable(ytdlpBinary).
Paths(homePath). Paths(homePath).
Paths(tempPath). Paths(tempPath).
RecodeVideo("mp4"). RecodeVideo("mp4").
ProgressFunc(100*time.Millisecond, func(prog ytdlp.ProgressUpdate) { ProgressFunc(100*time.Millisecond, func(prog ytdlp.ProgressUpdate) {
// Detect phase transition -- differentiate "downloading" as the main download if prog.Status == ytdlp.ProgressStatusFinished ||
// and "post processing" when the file name changes, preventing it from appearing "reset" prog.Status == ytdlp.ProgressStatusStarting ||
prog.Status == ytdlp.ProgressStatusError {
return
}
phase := "downloading" phase := "downloading"
if prog.Status == ytdlp.ProgressStatusDownloading && prog.Percent() == 0.0 { if prog.Status == ytdlp.ProgressStatusPostProcessing {
// If we already had progress, it's likely post-processing
if lastPhase == "downloading" {
phase = "post-processing"
}
} else if prog.Status != ytdlp.ProgressStatusDownloading {
phase = "post-processing" phase = "post-processing"
} }
lastPhase = phase mu.Lock()
currentPhase = phase
progressChan <- ProgressUpdate{ currentFilename = prog.Filename
Status: prog.Status, mu.Unlock()
Percent: prog.PercentString(),
PercentFloat: prog.Percent(),
ETA: prog.ETA(),
Filename: prog.Filename,
Phase: phase,
}
}). }).
Output("%(title)s.%(ext)s") Output("%(title)s.%(ext)s")