replace progress percentage with actual file size
This commit is contained in:
@@ -48,9 +48,8 @@ func startAsyncDownload(s *discordgo.Session, i *discordgo.InteractionCreate, ur
|
||||
|
||||
// Handle progress and results asynchronously
|
||||
go func() {
|
||||
// First update the original ephemeral message with "Processing..."
|
||||
_, err := s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
||||
Content: ptr(fmt.Sprintf("Downloading video: %s", renderProgressBar(0))),
|
||||
Content: ptr("⏬ downloading: starting..."),
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Error updating interaction: %v", err)
|
||||
@@ -63,35 +62,28 @@ func startAsyncDownload(s *discordgo.Session, i *discordgo.InteractionCreate, ur
|
||||
progressChan = nil
|
||||
continue
|
||||
}
|
||||
|
||||
var content string
|
||||
if prog.Phase == "post-processing" {
|
||||
phaseEmoji := "⚙️"
|
||||
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)
|
||||
}
|
||||
content = "⚙️ post-processing"
|
||||
} else {
|
||||
phaseEmoji := "⏬"
|
||||
content := fmt.Sprintf("%s %s: %s [eta: %s]\n📄 %s",
|
||||
phaseEmoji,
|
||||
prog.Phase,
|
||||
renderProgressBar(prog.PercentFloat),
|
||||
prog.ETA,
|
||||
prog.Filename)
|
||||
var progressStr string
|
||||
if prog.DownloadedBytes > 0 {
|
||||
progressStr = formatBytes(prog.DownloadedBytes) + " downloaded"
|
||||
} else {
|
||||
progressStr = "starting..."
|
||||
}
|
||||
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:
|
||||
// Handle completion
|
||||
if result.Success {
|
||||
_, err = s.FollowupMessageCreate(i.Interaction, false, &discordgo.WebhookParams{
|
||||
Content: "📥 Video downloaded: " + result.URL,
|
||||
|
||||
22
app/misc.go
22
app/misc.go
@@ -1,19 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
import "fmt"
|
||||
|
||||
// Helper function to create string pointer
|
||||
func ptr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
const progressBarLength = 20
|
||||
|
||||
func renderProgressBar(percent float64) string {
|
||||
filled := int(float64(progressBarLength) * percent / 100)
|
||||
bar := strings.Repeat("█", filled) + strings.Repeat(" ", progressBarLength-filled)
|
||||
return fmt.Sprintf("[%s] %.0f%%", bar, percent)
|
||||
func formatBytes(b int) string {
|
||||
switch {
|
||||
case b >= 1<<30:
|
||||
return fmt.Sprintf("%.1f GB", float64(b)/float64(1<<30))
|
||||
case b >= 1<<20:
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
13
app/types.go
13
app/types.go
@@ -1,11 +1,5 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/lrstanley/go-ytdlp"
|
||||
)
|
||||
|
||||
type DownloadOptions struct {
|
||||
EmbedThumbnail bool
|
||||
IncludeSubtitles bool
|
||||
@@ -35,12 +29,9 @@ type FormatOptions struct {
|
||||
}
|
||||
|
||||
type ProgressUpdate struct {
|
||||
Status ytdlp.ProgressStatus
|
||||
Percent string
|
||||
PercentFloat float64
|
||||
ETA time.Duration
|
||||
Filename string
|
||||
Phase string
|
||||
DownloadedBytes int
|
||||
Filename string
|
||||
}
|
||||
|
||||
// InteractionState holds the state for a specific interaction
|
||||
|
||||
82
app/ytdlp.go
82
app/ytdlp.go
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lrstanley/go-ytdlp"
|
||||
@@ -95,42 +97,80 @@ func GetFormats(url string) (*FormatOptions, error) {
|
||||
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) {
|
||||
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
|
||||
tempPath := "temp:" + temp_dir
|
||||
|
||||
var lastPhase string
|
||||
|
||||
dl := ytdlp.New().
|
||||
SetExecutable(ytdlpBinary).
|
||||
Paths(homePath).
|
||||
Paths(tempPath).
|
||||
RecodeVideo("mp4").
|
||||
ProgressFunc(100*time.Millisecond, func(prog ytdlp.ProgressUpdate) {
|
||||
// Detect phase transition -- differentiate "downloading" as the main download
|
||||
// and "post processing" when the file name changes, preventing it from appearing "reset"
|
||||
if prog.Status == ytdlp.ProgressStatusFinished ||
|
||||
prog.Status == ytdlp.ProgressStatusStarting ||
|
||||
prog.Status == ytdlp.ProgressStatusError {
|
||||
return
|
||||
}
|
||||
|
||||
phase := "downloading"
|
||||
if prog.Status == ytdlp.ProgressStatusDownloading && prog.Percent() == 0.0 {
|
||||
// If we already had progress, it's likely post-processing
|
||||
if lastPhase == "downloading" {
|
||||
phase = "post-processing"
|
||||
}
|
||||
} else if prog.Status != ytdlp.ProgressStatusDownloading {
|
||||
if prog.Status == ytdlp.ProgressStatusPostProcessing {
|
||||
phase = "post-processing"
|
||||
}
|
||||
|
||||
lastPhase = phase
|
||||
|
||||
progressChan <- ProgressUpdate{
|
||||
Status: prog.Status,
|
||||
Percent: prog.PercentString(),
|
||||
PercentFloat: prog.Percent(),
|
||||
ETA: prog.ETA(),
|
||||
Filename: prog.Filename,
|
||||
Phase: phase,
|
||||
}
|
||||
mu.Lock()
|
||||
currentPhase = phase
|
||||
currentFilename = prog.Filename
|
||||
mu.Unlock()
|
||||
}).
|
||||
Output("%(title)s.%(ext)s")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user