diff --git a/app/main.go b/app/main.go index 1a4c463..58bc138 100644 --- a/app/main.go +++ b/app/main.go @@ -28,6 +28,7 @@ type DownloadResult struct { // startAsyncDownload initiates a download in a goroutine and handles progress updates func startAsyncDownload(s *discordgo.Session, i *discordgo.InteractionCreate, url, audioFormat, outputDir string) { + progressChan := make(chan ProgressUpdate, 1) resultChan := make(chan DownloadResult, 1) // Start download in goroutine @@ -47,7 +48,7 @@ func startAsyncDownload(s *discordgo.Session, i *discordgo.InteractionCreate, ur }() // Call downloadVideo (it panics on error instead of returning error) - downloadVideo(outputDir, url, DownloadOptions{EmbedThumbnail: true, IncludeSubtitles: true}) + downloadVideo(outputDir, url, DownloadOptions{EmbedThumbnail: true, IncludeSubtitles: true}, progressChan) // If we reach here, download was successful resultChan <- DownloadResult{ @@ -59,7 +60,7 @@ func startAsyncDownload(s *discordgo.Session, i *discordgo.InteractionCreate, ur } }() - // Handle results asynchronously + // Handle progress and results asynchronously go func() { // First update the original ephemeral message with "Processing..." _, err := s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{ @@ -69,31 +70,49 @@ func startAsyncDownload(s *discordgo.Session, i *discordgo.InteractionCreate, ur log.Printf("Error updating interaction: %v", err) } - result := <-resultChan - - if result.Success { - // Update ephemeral message with completion status - _, err = s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{ - Content: ptr("āœ… Download completed successfully!\nURL: " + result.URL + "\nAudio: " + result.Format), - }) - if err != nil { - log.Printf("Error updating interaction: %v", err) - } - - // Send non-ephemeral completion message - _, err = s.FollowupMessageCreate(i.Interaction, false, &discordgo.WebhookParams{ - Content: "šŸ“„ Video downloaded: " + result.URL, - }) - if err != nil { - log.Printf("Error sending public completion message: %v", err) - } - } else { - // Update ephemeral message with error - _, err = s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{ - Content: ptr("āŒ Download failed: " + result.Message + "\nURL: " + result.URL + "\nAudio: " + result.Format), - }) - if err != nil { - log.Printf("Error updating interaction: %v", err) + for { + select { + case prog, ok := <-progressChan: + if !ok { + progressChan = nil + continue + } + // Update message w/ phase and real time progress + phaseEmoji := "ā¬" + if prog.Phase == "post-processing" { + phaseEmoji = "āš™ļø" + } + content := fmt.Sprintf("%s %s\n%s @ %s [eta: %s]\nšŸ“„ %s", + phaseEmoji, + prog.Phase, + prog.Status, + prog.Percent, + prog.ETA, + prog.Filename) + _, 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, + }) + if err != nil { + log.Printf("Error updating interaction: %v", err) + } + } else { + _, err = s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{ + Content: ptr("āŒ Download failed: " + result.Message), + }) + if err != nil { + log.Printf("Error updating interaction: %v", err) + } + } + return } } }() diff --git a/app/ytdlp.go b/app/ytdlp.go index 6b641c8..41c1fd5 100644 --- a/app/ytdlp.go +++ b/app/ytdlp.go @@ -2,7 +2,6 @@ package main import ( "context" - "fmt" "time" "github.com/lrstanley/go-ytdlp" @@ -13,19 +12,45 @@ type DownloadOptions struct { IncludeSubtitles bool } -func downloadVideo(out_dir, url string, opts DownloadOptions) { +type ProgressUpdate struct { + Status ytdlp.ProgressStatus + Percent string + ETA time.Duration + Filename string + Phase string +} + +func downloadVideo(out_dir, url string, opts DownloadOptions, progressChan chan<- ProgressUpdate) { + defer close(progressChan) + + var lastPhase string + dl := ytdlp.New(). SetWorkDir(out_dir). FormatSort("res,ext:mp4:m4a"). RecodeVideo("mp4"). ProgressFunc(100*time.Millisecond, func(prog ytdlp.ProgressUpdate) { - fmt.Printf( - "%s @ %s [eta: %s] :: %s\n", - prog.Status, - prog.PercentString(), - prog.ETA(), - prog.Filename, - ) + // Detect phase transition -- differentiate "downloading" as the main download + // and "post processing" when the file name changes, preventing it from appearing "reset" + 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 { + phase = "post-processing" + } + + lastPhase = phase + + progressChan <- ProgressUpdate{ + Status: prog.Status, + Percent: prog.PercentString(), + ETA: prog.ETA(), + Filename: prog.Filename, + Phase: phase, + } }). Output("%(title)s.%(ext)s")