From f5204b95543b38459ecc2c90b826bbe1522e926a Mon Sep 17 00:00:00 2001 From: db123 Date: Fri, 26 Jun 2026 12:12:33 +0330 Subject: [PATCH] fix: show bitrate for audio-only formats, filter subtitle languages, improve feedback - Prioritize IsAudioOnly check in formatLabelForDisplay so bitrate is shown instead of yt-dlp's generic 'audio only' resolution - Only collect languages from formats with audio/video to prevent subtitle-only tracks from triggering the language selection prompt - Show 'Fetching metadata...' message before GetMediaInfo and edit with results, improving UX for URL input - Add test coverage for audio-only format labels --- internal/dl/ytdlp.go | 4 +- internal/handler/download.go | 75 ++++++++++++++++++++------------ internal/handler/handler_test.go | 12 +++++ 3 files changed, 63 insertions(+), 28 deletions(-) diff --git a/internal/dl/ytdlp.go b/internal/dl/ytdlp.go index eb6390f..5553697 100644 --- a/internal/dl/ytdlp.go +++ b/internal/dl/ytdlp.go @@ -171,7 +171,9 @@ func (c *Client) GetMediaInfo(url string) (*domain.MediaInfo, error) { for _, f := range info.Formats { ff := c.convertFormat(f) mi.Formats = append(mi.Formats, ff) - if ff.Language != "" && !langSet[ff.Language] { + // Only consider languages from formats with audio or video to avoid + // prompting for subtitle-only or auto-generated caption languages. + if (ff.HasAudio || ff.HasVideo) && ff.Language != "" && !langSet[ff.Language] { langSet[ff.Language] = true mi.Languages = append(mi.Languages, ff.Language) } diff --git a/internal/handler/download.go b/internal/handler/download.go index ea87655..1ee70af 100644 --- a/internal/handler/download.go +++ b/internal/handler/download.go @@ -72,6 +72,16 @@ func (h *Handler) handleURLInput(ctx context.Context, chatID int64, text string, return } + // Show a temporary status message that we will edit with results later. + fetchingMsg, _ := h.Bot.SendMessage(ctx, &tgbot.SendMessageParams{ + ChatID: chatID, + Text: "Fetching metadata...", + }) + fetchingMsgID := 0 + if fetchingMsg != nil { + fetchingMsgID = fetchingMsg.ID + } + info, err := h.YtDlp.GetMediaInfo(url) if err != nil { errMsg := err.Error() @@ -86,57 +96,68 @@ func (h *Handler) handleURLInput(ctx context.Context, chatID int64, text string, {{Text: "Cancel", CallbackData: "pending_cancel"}}, }, } - h.Bot.SendMessage(ctx, &tgbot.SendMessageParams{ - ChatID: chatID, Text: "This content is DRM protected and no YouTube alternative was found automatically. Try searching by name:", ReplyMarkup: kb, - }) + h.editText(ctx, chatID, fetchingMsgID, "This content is DRM protected and no YouTube alternative was found automatically. Try searching by name:", &kb) return } - h.sendText(ctx, chatID, fmt.Sprintf("This content is DRM protected. Found on YouTube: %s", ytTitle)) + h.editText(ctx, chatID, fetchingMsgID, fmt.Sprintf("This content is DRM protected. Found on YouTube: %s", ytTitle), nil) info, err = h.YtDlp.GetMediaInfo(youtubeURL) if err != nil { - h.sendText(ctx, chatID, fmt.Sprintf("YouTube fallback failed: %s", err.Error())) + h.editText(ctx, chatID, fetchingMsgID, fmt.Sprintf("YouTube fallback failed: %s", err.Error()), nil) return } url = youtubeURL } else { switch { case strings.Contains(errMsg, "Unsupported URL") || strings.Contains(errMsg, "is not a valid URL"): - h.sendText(ctx, chatID, "This URL is not supported.") + h.editText(ctx, chatID, fetchingMsgID, "This URL is not supported.", nil) case strings.Contains(errMsg, "HTTP Error 403") || strings.Contains(errMsg, "cookies"): - h.sendText(ctx, chatID, "This content requires authentication (cookies).") + h.editText(ctx, chatID, fetchingMsgID, "This content requires authentication (cookies).", nil) case strings.Contains(errMsg, "live"): - h.sendText(ctx, chatID, "Cannot download live streams.") + h.editText(ctx, chatID, fetchingMsgID, "Cannot download live streams.", nil) default: - h.sendText(ctx, chatID, fmt.Sprintf("Error: %s", errMsg)) + h.editText(ctx, chatID, fetchingMsgID, fmt.Sprintf("Error: %s", errMsg), nil) } return } } if info.IsPlaylist { + h.editText(ctx, chatID, fetchingMsgID, fmt.Sprintf("Playlist detected: %s (%d videos)", info.Title, info.PlaylistCount), nil) h.handlePlaylist(ctx, chatID, userID, url, info) return } if len(info.Formats) == 0 { - h.sendText(ctx, chatID, "No formats available for this URL.") + h.editText(ctx, chatID, fetchingMsgID, "No formats available for this URL.", nil) return } // Reject if user has already exhausted quota (limit of 0 = unlimited). - q, err := h.Repo.GetOrCreateQuotas(userID) - if err == nil { + q, qErr := h.Repo.GetOrCreateQuotas(userID) + if qErr == nil { q = h.resetQuotasIfNeeded(q) if (q.DailyLimitMB > 0 && q.DailyUsedMB >= q.DailyLimitMB) || (q.WeeklyLimitMB > 0 && q.WeeklyUsedMB >= q.WeeklyLimitMB) || (q.MonthlyLimitMB > 0 && q.MonthlyUsedMB >= q.MonthlyLimitMB) { - h.sendText(ctx, chatID, fmt.Sprintf( + h.editText(ctx, chatID, fetchingMsgID, fmt.Sprintf( "Quota exceeded. Daily: %d/%d MB, Weekly: %d/%d MB, Monthly: %d/%d MB.", - q.DailyUsedMB, q.DailyLimitMB, q.WeeklyUsedMB, q.WeeklyLimitMB, q.MonthlyUsedMB, q.MonthlyLimitMB)) + q.DailyUsedMB, q.DailyLimitMB, q.WeeklyUsedMB, q.WeeklyLimitMB, q.MonthlyUsedMB, q.MonthlyLimitMB), nil) return } } + // Edit the fetching message to show the media title, then send a separate photo preview. + previewText := fmt.Sprintf("Title: %s", info.Title) + if info.Uploader != "" { + previewText += fmt.Sprintf("\nChannel: %s", info.Uploader) + } + if info.Duration > 0 { + previewText += fmt.Sprintf("\nDuration: %s", formatDuration(info.Duration)) + } + h.editText(ctx, chatID, fetchingMsgID, previewText, nil) + + h.sendMediaPreview(ctx, chatID, info) + ss := &stepState{ URL: url, MediaInfo: info, @@ -144,8 +165,6 @@ func (h *Handler) handleURLInput(ctx context.Context, chatID int64, text string, CreatedAt: time.Now(), } - h.sendMediaPreview(ctx, chatID, info) - ss.FormatIDs, _ = buildFormatList(info.Formats) h.setUserStepState(chatID, ss) labels := make([]string, len(ss.FormatIDs)) @@ -675,6 +694,19 @@ func findFormatByID(formats []domain.Format, id string) *domain.Format { } func formatLabelForDisplay(f domain.Format) string { + if f.IsAudioOnly { + label := f.Bitrate + if label == "" { + label = f.ID + if f.Extension != "" { + label += " (" + f.Extension + ")" + } + } + if f.Filesize > 0 { + label += fmt.Sprintf(" (%s)", formatBytes(f.Filesize)) + } + return label + } if f.Resolution != "" { label := f.Resolution if f.Filesize > 0 { @@ -689,17 +721,6 @@ func formatLabelForDisplay(f domain.Format) string { } return label } - // Audio-only with no bitrate: show format ID + extension - if f.IsAudioOnly { - label := f.ID - if f.Extension != "" { - label += " (" + f.Extension + ")" - } - if f.Filesize > 0 { - label += fmt.Sprintf(" (%s)", formatBytes(f.Filesize)) - } - return label - } return f.ID } diff --git a/internal/handler/handler_test.go b/internal/handler/handler_test.go index 196b70a..756fa84 100644 --- a/internal/handler/handler_test.go +++ b/internal/handler/handler_test.go @@ -194,6 +194,18 @@ func TestFormatLabelForDisplay(t *testing.T) { f: domain.Format{ID: "137"}, want: "137", }, + { + f: domain.Format{IsAudioOnly: true, Bitrate: "128k", Filesize: 1048576, Resolution: "audio only"}, + want: "128k (1.0MB)", + }, + { + f: domain.Format{IsAudioOnly: true, Bitrate: "128k", Resolution: "audio only"}, + want: "128k", + }, + { + f: domain.Format{IsAudioOnly: true, ID: "140", Extension: "m4a", Filesize: 1048576, Resolution: "audio only"}, + want: "140 (m4a) (1.0MB)", + }, } for _, tt := range tests { got := formatLabelForDisplay(tt.f)