feat(local-tool): yt-dlp 錯誤訊息友善化 + 載入逾時提示

ResolveWithYTDLP 改善:
- 新增 friendlyYTDLPError() 把 yt-dlp 的技術性 stderr 訊息轉成繁中提示
- 涵蓋 9 種常見失敗原因:
  年齡限制 / 私人影片 / 已移除 / 版權 / 直播 / 首播 / DRM / 頻率限制 / 無格式
- 錯誤訊息同時附上原始 stderr(方便 debug)
- yt-dlp 未安裝時改用繁中提示

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-04-13 00:56:15 +08:00
parent f13848cf0b
commit c0317225ca

View File

@ -92,27 +92,53 @@ func NewVideoSourceFromURLWithSeek(rawURL string, fps float64, seekSeconds float
// from platforms like YouTube, Vimeo, etc.
// Returns the resolved direct URL or an error.
func ResolveWithYTDLP(rawURL string) (string, error) {
// yt-dlp -f "best[ext=mp4]/best" --get-url <URL>
cmd := exec.Command("yt-dlp", "-f", "best[ext=mp4]/best", "--get-url", rawURL)
out, err := cmd.Output()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
return "", fmt.Errorf("yt-dlp failed: %s", string(exitErr.Stderr))
stderr := strings.TrimSpace(string(exitErr.Stderr))
return "", fmt.Errorf("%s\n%s", friendlyYTDLPError(stderr), stderr)
}
return "", fmt.Errorf("yt-dlp not available: %w", err)
return "", fmt.Errorf("yt-dlp 未安裝或無法執行: %w", err)
}
resolved := strings.TrimSpace(string(out))
if resolved == "" {
return "", fmt.Errorf("yt-dlp returned empty URL")
return "", fmt.Errorf("yt-dlp 無法取得影片下載連結(影片可能有地區限制或需要登入)")
}
// yt-dlp may return multiple lines (video + audio); take only the first
if idx := strings.Index(resolved, "\n"); idx > 0 {
resolved = resolved[:idx]
}
return resolved, nil
}
// friendlyYTDLPError 把 yt-dlp 的技術性錯誤訊息轉成使用者能理解的提示。
func friendlyYTDLPError(stderr string) string {
s := strings.ToLower(stderr)
switch {
case strings.Contains(s, "sign in") || strings.Contains(s, "age"):
return "此影片需要登入 YouTube 帳號才能觀看(例如年齡限制),無法直接使用"
case strings.Contains(s, "private"):
return "此影片為私人影片,無法存取"
case strings.Contains(s, "unavailable") || strings.Contains(s, "not available"):
return "此影片無法取得(可能已被移除或在你的地區不可用)"
case strings.Contains(s, "copyright"):
return "此影片因版權限制無法下載"
case strings.Contains(s, "live"):
return "此影片為直播串流,目前不支援直播推論"
case strings.Contains(s, "premiere"):
return "此影片為首播Premiere尚未開始或格式不支援"
case strings.Contains(s, "drm") || strings.Contains(s, "protected"):
return "此影片有 DRM 保護,無法下載"
case strings.Contains(s, "rate limit") || strings.Contains(s, "429"):
return "YouTube 暫時限制了請求頻率,請稍後再試"
case strings.Contains(s, "no video formats"):
return "找不到可用的影片格式"
default:
return "無法解析此影片連結,請確認 URL 是否正確且影片為公開狀態"
}
}
func newVideoSource(input string, fps float64, isURL bool, seekSeconds float64) (*VideoSource, error) {
if fps <= 0 {
fps = 15