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
|
// 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)
|
content = fmt.Sprintf("Downloading Video: %s", progressStr)
|
||||||
|
}
|
||||||
|
|
||||||
_, err := s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
_, err := s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
|
||||||
Content: ptr(content),
|
Content: ptr(content),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error updating progress: %v", err)
|
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,
|
||||||
|
|||||||
22
app/misc.go
22
app/misc.go
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
app/types.go
13
app/types.go
@@ -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
|
|
||||||
Percent string
|
|
||||||
PercentFloat float64
|
|
||||||
ETA time.Duration
|
|
||||||
Filename string
|
|
||||||
Phase string
|
Phase string
|
||||||
|
DownloadedBytes int
|
||||||
|
Filename string
|
||||||
}
|
}
|
||||||
|
|
||||||
// InteractionState holds the state for a specific interaction
|
// InteractionState holds the state for a specific interaction
|
||||||
|
|||||||
82
app/ytdlp.go
82
app/ytdlp.go
@@ -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")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user