Skip to content

Commit 87ac296

Browse files
committed
feat(bilibili-analyzer): enhance prepare script with robust tool detection and internationalization
- Add UTF-8 encoding support for Windows console output - Implement find_yt_dlp() function to detect yt-dlp as command or Python module - Implement find_ffmpeg() function with fallback to common Windows installation paths - Replace hardcoded tool commands with dynamic detection and helpful error messages - Translate all user-facing strings from Chinese to English for broader accessibility - Add improved error handling with detailed installation instructions for missing dependencies - Add progress indicators and formatted output with visual separators - Add -y flag to ffmpeg to automatically overwrite existing files - Improve exception handling to catch and report unexpected errors gracefully - Enhance help text and examples with clearer descriptions - Add UTF-8 encoding declaration at file header
1 parent 42c2409 commit 87ac296

1 file changed

Lines changed: 124 additions & 40 deletions

File tree

skills/tools/bilibili-analyzer/scripts/prepare.py

Lines changed: 124 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,216 @@
11
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
23
"""
34
Bilibili Video Downloader and Frame Extractor
4-
下载B站视频并拆解成帧图片
5+
Download Bilibili videos and extract frames
56
"""
67

78
import os
89
import sys
910
import subprocess
1011
import argparse
12+
import shutil
13+
14+
# Fix Windows console encoding
15+
if sys.platform == "win32":
16+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
17+
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
18+
19+
20+
def find_yt_dlp():
21+
"""Find yt-dlp executable or module
22+
23+
Returns:
24+
list: Command to run yt-dlp, or None if not found
25+
"""
26+
# Try direct command first
27+
if shutil.which("yt-dlp"):
28+
return ["yt-dlp"]
29+
30+
# Try as Python module
31+
try:
32+
result = subprocess.run(
33+
[sys.executable, "-m", "yt_dlp", "--version"],
34+
capture_output=True,
35+
timeout=10
36+
)
37+
if result.returncode == 0:
38+
return [sys.executable, "-m", "yt_dlp"]
39+
except Exception:
40+
pass
41+
42+
return None
43+
44+
45+
def find_ffmpeg():
46+
"""Find ffmpeg executable
47+
48+
Returns:
49+
str: Path to ffmpeg, or None if not found
50+
"""
51+
if shutil.which("ffmpeg"):
52+
return "ffmpeg"
53+
54+
# Common Windows paths
55+
common_paths = [
56+
r"C:\ffmpeg\bin\ffmpeg.exe",
57+
r"C:\Program Files\ffmpeg\bin\ffmpeg.exe",
58+
r"C:\tools\ffmpeg\bin\ffmpeg.exe",
59+
]
60+
for path in common_paths:
61+
if os.path.exists(path):
62+
return path
63+
64+
return None
1165

1266

1367
def download_video(url: str, output_path: str = "video.mp4") -> bool:
14-
"""下载B站视频
68+
"""Download Bilibili video
1569
1670
Args:
17-
url: B站视频URL
18-
output_path: 输出文件路径
71+
url: Bilibili video URL
72+
output_path: Output file path
1973
2074
Returns:
21-
是否下载成功
75+
Whether download succeeded
2276
"""
23-
print(f"[INFO] 正在下载视频: {url}")
77+
print(f"[INFO] Downloading video: {url}")
2478

25-
cmd = [
26-
"yt-dlp",
79+
yt_dlp_cmd = find_yt_dlp()
80+
if not yt_dlp_cmd:
81+
print("[ERROR] yt-dlp not found!")
82+
print(" Install with: pip install yt-dlp")
83+
print(f" Current Python: {sys.executable}")
84+
return False
85+
86+
cmd = yt_dlp_cmd + [
2787
"-f", "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best",
2888
"-o", output_path,
89+
"--no-warnings",
2990
url
3091
]
3192

3293
try:
94+
print(f"[INFO] Running: {' '.join(cmd[:3])}...")
3395
result = subprocess.run(cmd, check=True)
34-
print(f"[OK] 视频下载完成: {output_path}")
96+
print(f"[OK] Video downloaded: {output_path}")
3597
return True
3698
except subprocess.CalledProcessError as e:
37-
print(f"[ERROR] 下载失败: {e}")
99+
print(f"[ERROR] Download failed: {e}")
38100
return False
39-
except FileNotFoundError:
40-
print("[ERROR] yt-dlp 未安装,请运行: pip install yt-dlp")
101+
except Exception as e:
102+
print(f"[ERROR] Unexpected error: {e}")
41103
return False
42104

43105

44106
def extract_frames(video_path: str, output_dir: str = "./images", fps: float = 1.0) -> bool:
45-
"""从视频提取帧图片
107+
"""Extract frames from video
46108
47109
Args:
48-
video_path: 视频文件路径
49-
output_dir: 输出目录
50-
fps: 每秒提取帧数,默认1帧/秒
110+
video_path: Video file path
111+
output_dir: Output directory
112+
fps: Frames per second, default 1
51113
52114
Returns:
53-
是否提取成功
115+
Whether extraction succeeded
54116
"""
55-
print(f"[INFO] 正在提取帧图片 (fps={fps})")
117+
print(f"[INFO] Extracting frames (fps={fps})")
118+
119+
ffmpeg_cmd = find_ffmpeg()
120+
if not ffmpeg_cmd:
121+
print("[ERROR] ffmpeg not found!")
122+
print(" Windows: choco install ffmpeg / scoop install ffmpeg")
123+
print(" macOS: brew install ffmpeg")
124+
print(" Linux: sudo apt install ffmpeg")
125+
return False
56126

57-
# 创建输出目录
127+
# Create output directory
58128
os.makedirs(output_dir, exist_ok=True)
59129

60130
output_pattern = os.path.join(output_dir, "frame_%04d.jpg")
61131

62132
cmd = [
63-
"ffmpeg",
133+
ffmpeg_cmd,
64134
"-i", video_path,
65135
"-vf", f"fps={fps}",
66136
"-q:v", "2",
137+
"-y", # Overwrite existing files
67138
output_pattern
68139
]
69140

70141
try:
142+
print(f"[INFO] Running ffmpeg...")
71143
result = subprocess.run(cmd, check=True, capture_output=True)
72144

73-
# 统计生成的图片数量
145+
# Count generated images
74146
frame_count = len([f for f in os.listdir(output_dir) if f.startswith("frame_") and f.endswith(".jpg")])
75-
print(f"[OK] 帧提取完成: {frame_count} 张图片保存到 {output_dir}/")
147+
print(f"[OK] Frames extracted: {frame_count} images saved to {output_dir}/")
76148
return True
77149
except subprocess.CalledProcessError as e:
78-
print(f"[ERROR] 帧提取失败: {e.stderr.decode() if e.stderr else e}")
150+
stderr = e.stderr.decode('utf-8', errors='replace') if e.stderr else str(e)
151+
print(f"[ERROR] Frame extraction failed: {stderr}")
79152
return False
80-
except FileNotFoundError:
81-
print("[ERROR] ffmpeg 未安装,请安装 ffmpeg")
153+
except Exception as e:
154+
print(f"[ERROR] Unexpected error: {e}")
82155
return False
83156

84157

85158
def main():
86159
parser = argparse.ArgumentParser(
87-
description="下载B站视频并拆解成帧图片",
160+
description="Download Bilibili video and extract frames",
88161
formatter_class=argparse.RawDescriptionHelpFormatter,
89162
epilog="""
90-
示例:
163+
Examples:
91164
python prepare.py "https://www.bilibili.com/video/BV1xx411c7mD"
92165
python prepare.py "https://www.bilibili.com/video/BV1xx411c7mD" --fps 0.5
93166
python prepare.py "https://www.bilibili.com/video/BV1xx411c7mD" -o ./output
94167
"""
95168
)
96169

97-
parser.add_argument("url", help="B站视频URL")
98-
parser.add_argument("-o", "--output", default=".", help="输出目录,默认当前目录")
99-
parser.add_argument("--fps", type=float, default=1.0, help="每秒提取帧数,默认1")
100-
parser.add_argument("--video-only", action="store_true", help="只下载视频,不提取帧")
101-
parser.add_argument("--frames-only", action="store_true", help="只提取帧(需要已有video.mp4")
170+
parser.add_argument("url", help="Bilibili video URL")
171+
parser.add_argument("-o", "--output", default=".", help="Output directory (default: current)")
172+
parser.add_argument("--fps", type=float, default=1.0, help="Frames per second (default: 1)")
173+
parser.add_argument("--video-only", action="store_true", help="Only download video, skip frame extraction")
174+
parser.add_argument("--frames-only", action="store_true", help="Only extract frames (requires existing video.mp4)")
102175

103176
args = parser.parse_args()
104177

105-
# 设置路径
178+
# Set paths
106179
output_dir = args.output
107180
video_path = os.path.join(output_dir, "video.mp4")
108181
images_dir = os.path.join(output_dir, "images")
109182

110-
# 创建输出目录
183+
# Create output directory
111184
os.makedirs(output_dir, exist_ok=True)
112185

113-
# 下载视频
186+
print("=" * 50)
187+
print("Bilibili Video Analyzer - Prepare Script")
188+
print("=" * 50)
189+
print(f"URL: {args.url}")
190+
print(f"Output: {output_dir}")
191+
print(f"FPS: {args.fps}")
192+
print("=" * 50)
193+
194+
# Download video
114195
if not args.frames_only:
115196
if not download_video(args.url, video_path):
116197
sys.exit(1)
117198

118-
# 提取帧
199+
# Extract frames
119200
if not args.video_only:
120201
if not os.path.exists(video_path):
121-
print(f"[ERROR] 视频文件不存在: {video_path}")
202+
print(f"[ERROR] Video file not found: {video_path}")
122203
sys.exit(1)
123204

124205
if not extract_frames(video_path, images_dir, args.fps):
125206
sys.exit(1)
126207

127-
print("\n[OK] 完成!")
128-
print(f" 视频: {video_path}")
129-
print(f" 图片: {images_dir}/")
208+
print("")
209+
print("=" * 50)
210+
print("[OK] Done!")
211+
print(f" Video: {video_path}")
212+
print(f" Images: {images_dir}/")
213+
print("=" * 50)
130214

131215

132216
if __name__ == "__main__":

0 commit comments

Comments
 (0)