hchcsuim commited on
Commit
d2b4586
·
1 Parent(s): 2ab7dcf

Fix YouTube example URL processing in Spaces environment

Browse files
Files changed (2) hide show
  1. app.py +16 -2
  2. youtube_api.py +52 -34
app.py CHANGED
@@ -168,8 +168,11 @@ def process_youtube_url(youtube_url, user_api_key=None):
168
  # 處理 YouTube URL
169
  print(f"Processing YouTube URL: {youtube_url}")
170
 
171
- if is_spaces and not youtube_api_key:
172
- # Spaces 環境中且沒有 API 金鑰,顯示警告
 
 
 
173
  print("Warning: YouTube download is not supported in Hugging Face Spaces without an API key.")
174
  raise gr.Error("YouTube 下載在 Hugging Face Spaces 中需要 API 金鑰。請在上方的 'YouTube API Key Settings' 中輸入您的 API 金鑰。\n\nYouTube download in Hugging Face Spaces requires an API key. Please enter your API key in the 'YouTube API Key Settings' section above.")
175
 
@@ -865,6 +868,17 @@ with gr.Blocks(css=compact_css, theme=gr.themes.Default(spacing_size=gr.themes.s
865
  # 添加範例,點擊時自動處理
866
  def process_example_url(url):
867
  """處理範例 URL 的函數"""
 
 
 
 
 
 
 
 
 
 
 
868
  # 獲取 API 金鑰
869
  api_key = youtube_api_key_input.value if hasattr(youtube_api_key_input, 'value') else None
870
  # 處理 URL
 
168
  # 處理 YouTube URL
169
  print(f"Processing YouTube URL: {youtube_url}")
170
 
171
+ # 檢查是否是在啟動時自動調用(緩存範例)
172
+ is_startup = not hasattr(youtube_api_key_input, 'value') if 'youtube_api_key_input' in globals() else False
173
+
174
+ if is_spaces and not youtube_api_key and not is_startup:
175
+ # 在 Spaces 環境中且沒有 API 金鑰,顯示警告(但不是在啟動時)
176
  print("Warning: YouTube download is not supported in Hugging Face Spaces without an API key.")
177
  raise gr.Error("YouTube 下載在 Hugging Face Spaces 中需要 API 金鑰。請在上方的 'YouTube API Key Settings' 中輸入您的 API 金鑰。\n\nYouTube download in Hugging Face Spaces requires an API key. Please enter your API key in the 'YouTube API Key Settings' section above.")
178
 
 
868
  # 添加範例,點擊時自動處理
869
  def process_example_url(url):
870
  """處理範例 URL 的函數"""
871
+ # 檢查是否在 Hugging Face Spaces 環境中
872
+ is_spaces = os.environ.get("SPACE_ID") is not None
873
+
874
+ # 檢查是否是在啟動時自動調用(緩存範例)
875
+ is_startup = not hasattr(youtube_api_key_input, 'value')
876
+
877
+ # 如果是在 Spaces 環境中啟動時調用,則不處理 URL
878
+ if is_spaces and is_startup:
879
+ print("Skipping example URL processing during startup in Spaces environment")
880
+ return gr.update(visible=False, value=None), gr.update(visible=False, value=None)
881
+
882
  # 獲取 API 金鑰
883
  api_key = youtube_api_key_input.value if hasattr(youtube_api_key_input, 'value') else None
884
  # 處理 URL
youtube_api.py CHANGED
@@ -39,28 +39,28 @@ def get_video_info(video_id):
39
  """使用 YouTube Data API 獲取視頻信息"""
40
  if not YOUTUBE_API_KEY:
41
  raise ValueError("YouTube API 金鑰未設置。請先調用 set_api_key() 函數。")
42
-
43
  try:
44
  youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=YOUTUBE_API_KEY)
45
-
46
  # 獲取視頻詳細信息
47
  video_response = youtube.videos().list(
48
  part="snippet,contentDetails,statistics",
49
  id=video_id
50
  ).execute()
51
-
52
  # 檢查是否找到視頻
53
  if not video_response.get("items"):
54
  return None
55
-
56
  video_info = video_response["items"][0]
57
  snippet = video_info["snippet"]
58
  content_details = video_info["contentDetails"]
59
-
60
  # 解析時長
61
  duration_str = content_details["duration"] # 格式: PT#H#M#S
62
  duration_seconds = parse_duration(duration_str)
63
-
64
  # 返回視頻信息
65
  return {
66
  "title": snippet["title"],
@@ -70,7 +70,7 @@ def get_video_info(video_id):
70
  "duration": duration_seconds,
71
  "thumbnail": snippet["thumbnails"]["high"]["url"] if "high" in snippet["thumbnails"] else snippet["thumbnails"]["default"]["url"]
72
  }
73
-
74
  except HttpError as e:
75
  print(f"YouTube API 錯誤: {e}")
76
  return None
@@ -82,50 +82,50 @@ def parse_duration(duration_str):
82
  """解析 ISO 8601 時長格式 (PT#H#M#S)"""
83
  duration_str = duration_str[2:] # 移除 "PT"
84
  hours, minutes, seconds = 0, 0, 0
85
-
86
  # 解析小時
87
  if "H" in duration_str:
88
  hours_part = duration_str.split("H")[0]
89
  hours = int(hours_part)
90
  duration_str = duration_str.split("H")[1]
91
-
92
  # 解析分鐘
93
  if "M" in duration_str:
94
  minutes_part = duration_str.split("M")[0]
95
  minutes = int(minutes_part)
96
  duration_str = duration_str.split("M")[1]
97
-
98
  # 解析秒
99
  if "S" in duration_str:
100
  seconds_part = duration_str.split("S")[0]
101
  seconds = int(seconds_part)
102
-
103
  # 計算總秒數
104
  total_seconds = hours * 3600 + minutes * 60 + seconds
105
  return total_seconds
106
 
107
  def download_audio(video_id, api_info=None):
108
  """下載 YouTube 視頻的音頻
109
-
110
  Args:
111
  video_id: YouTube 視頻 ID
112
  api_info: 從 API 獲取的視頻信息 (可選)
113
-
114
  Returns:
115
  tuple: (音頻文件路徑, 臨時目錄, 視頻時長)
116
  """
117
  # 使用固定的目錄來存儲下載的音訊文件
118
  download_dir = os.path.join(tempfile.gettempdir(), "youtube_downloads")
119
  os.makedirs(download_dir, exist_ok=True)
120
-
121
  # 使用視頻 ID 和時間戳作為文件名
122
  filename = f"youtube_{video_id}_{int(time.time())}"
123
  temp_dir = tempfile.mkdtemp()
124
-
125
  try:
126
  # 準備下載路徑
127
  temp_filepath_tmpl = os.path.join(download_dir, f"{filename}.%(ext)s")
128
-
129
  # 設置 yt-dlp 選項
130
  ydl_opts = {
131
  'format': 'bestaudio/best',
@@ -139,15 +139,15 @@ def download_audio(video_id, api_info=None):
139
  }],
140
  'ffmpeg_location': shutil.which("ffmpeg"),
141
  }
142
-
143
  # 檢查 ffmpeg
144
  if not ydl_opts['ffmpeg_location']:
145
  print("Warning: ffmpeg not found... / 警告:找不到 ffmpeg...")
146
-
147
  # 如果已經有 API 信息,使用它
148
  duration = api_info["duration"] if api_info else None
149
  title = api_info["title"] if api_info else "Unknown"
150
-
151
  # 下載音頻
152
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
153
  # 如果沒有 API 信息,從 yt-dlp 獲取
@@ -158,10 +158,10 @@ def download_audio(video_id, api_info=None):
158
  else:
159
  # 有 API 信息,直接下載
160
  ydl.download([f"https://www.youtube.com/watch?v={video_id}"])
161
-
162
  # 確定最終文件路徑
163
  final_filepath = os.path.join(download_dir, f"{filename}.mp3")
164
-
165
  # 檢查文件是否存在
166
  if os.path.exists(final_filepath):
167
  print(f"YouTube audio downloaded: {final_filepath}")
@@ -170,8 +170,8 @@ def download_audio(video_id, api_info=None):
170
  else:
171
  # 嘗試查找可能的文件
172
  potential_files = [
173
- os.path.join(download_dir, f)
174
- for f in os.listdir(download_dir)
175
  if f.startswith(filename) and f.endswith(".mp3")
176
  ]
177
  if potential_files:
@@ -180,7 +180,7 @@ def download_audio(video_id, api_info=None):
180
  return downloaded_path, temp_dir, duration
181
  else:
182
  raise FileNotFoundError(f"Audio file not found after download in {download_dir}")
183
-
184
  except Exception as e:
185
  print(f"Error downloading YouTube audio: {e}")
186
  if temp_dir and os.path.exists(temp_dir):
@@ -190,40 +190,58 @@ def download_audio(video_id, api_info=None):
190
  print(f"Error cleaning temp directory {temp_dir}: {cleanup_e}")
191
  return None, None, None
192
 
193
- def process_youtube_url(youtube_url):
194
  """處理 YouTube URL,獲取信息並下載音頻
195
-
196
  Args:
197
  youtube_url: YouTube 視頻 URL
198
-
 
199
  Returns:
200
  tuple: (音頻文件路徑, 視頻信息)
201
  """
202
  # 檢查 URL 是否有效
203
  if not youtube_url or not youtube_url.strip():
204
  return None, None
205
-
 
 
 
 
 
 
 
206
  # 提取視頻 ID
207
  video_id = extract_video_id(youtube_url)
208
  if not video_id:
209
  print(f"Invalid YouTube URL: {youtube_url}")
210
  return None, None
211
-
212
  # 檢查是否設置了 API 金鑰
213
  if YOUTUBE_API_KEY:
214
  # 使用 API 獲取視頻信息
215
  video_info = get_video_info(video_id)
216
  if not video_info:
217
  print(f"Could not get video info from API for: {video_id}")
 
 
 
 
 
218
  # 如果 API 失敗,嘗試直接下載
219
  audio_path, temp_dir, duration = download_audio(video_id)
220
  return audio_path, {"title": "Unknown", "duration": duration}
221
-
222
  # 使用 API 信息下載音頻
223
  audio_path, temp_dir, _ = download_audio(video_id, video_info)
224
  return audio_path, video_info
225
  else:
226
- # 沒有 API 金鑰,直接使用 yt-dlp
 
 
 
 
 
227
  print("No YouTube API key set, using yt-dlp directly")
228
  audio_path, temp_dir, duration = download_audio(video_id)
229
  return audio_path, {"title": "Unknown", "duration": duration}
@@ -233,13 +251,13 @@ if __name__ == "__main__":
233
  # 設置 API 金鑰(實際使用時應從環境變量或配置文件獲取)
234
  api_key = "YOUR_API_KEY"
235
  set_api_key(api_key)
236
-
237
  # 測試 URL
238
  test_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
239
-
240
  # 處理 URL
241
  audio_path, video_info = process_youtube_url(test_url)
242
-
243
  if audio_path and video_info:
244
  print(f"Downloaded: {audio_path}")
245
  print(f"Video info: {video_info}")
 
39
  """使用 YouTube Data API 獲取視頻信息"""
40
  if not YOUTUBE_API_KEY:
41
  raise ValueError("YouTube API 金鑰未設置。請先調用 set_api_key() 函數。")
42
+
43
  try:
44
  youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=YOUTUBE_API_KEY)
45
+
46
  # 獲取視頻詳細信息
47
  video_response = youtube.videos().list(
48
  part="snippet,contentDetails,statistics",
49
  id=video_id
50
  ).execute()
51
+
52
  # 檢查是否找到視頻
53
  if not video_response.get("items"):
54
  return None
55
+
56
  video_info = video_response["items"][0]
57
  snippet = video_info["snippet"]
58
  content_details = video_info["contentDetails"]
59
+
60
  # 解析時長
61
  duration_str = content_details["duration"] # 格式: PT#H#M#S
62
  duration_seconds = parse_duration(duration_str)
63
+
64
  # 返回視頻信息
65
  return {
66
  "title": snippet["title"],
 
70
  "duration": duration_seconds,
71
  "thumbnail": snippet["thumbnails"]["high"]["url"] if "high" in snippet["thumbnails"] else snippet["thumbnails"]["default"]["url"]
72
  }
73
+
74
  except HttpError as e:
75
  print(f"YouTube API 錯誤: {e}")
76
  return None
 
82
  """解析 ISO 8601 時長格式 (PT#H#M#S)"""
83
  duration_str = duration_str[2:] # 移除 "PT"
84
  hours, minutes, seconds = 0, 0, 0
85
+
86
  # 解析小時
87
  if "H" in duration_str:
88
  hours_part = duration_str.split("H")[0]
89
  hours = int(hours_part)
90
  duration_str = duration_str.split("H")[1]
91
+
92
  # 解析分鐘
93
  if "M" in duration_str:
94
  minutes_part = duration_str.split("M")[0]
95
  minutes = int(minutes_part)
96
  duration_str = duration_str.split("M")[1]
97
+
98
  # 解析秒
99
  if "S" in duration_str:
100
  seconds_part = duration_str.split("S")[0]
101
  seconds = int(seconds_part)
102
+
103
  # 計算總秒數
104
  total_seconds = hours * 3600 + minutes * 60 + seconds
105
  return total_seconds
106
 
107
  def download_audio(video_id, api_info=None):
108
  """下載 YouTube 視頻的音頻
109
+
110
  Args:
111
  video_id: YouTube 視頻 ID
112
  api_info: 從 API 獲取的視頻信息 (可選)
113
+
114
  Returns:
115
  tuple: (音頻文件路徑, 臨時目錄, 視頻時長)
116
  """
117
  # 使用固定的目錄來存儲下載的音訊文件
118
  download_dir = os.path.join(tempfile.gettempdir(), "youtube_downloads")
119
  os.makedirs(download_dir, exist_ok=True)
120
+
121
  # 使用視頻 ID 和時間戳作為文件名
122
  filename = f"youtube_{video_id}_{int(time.time())}"
123
  temp_dir = tempfile.mkdtemp()
124
+
125
  try:
126
  # 準備下載路徑
127
  temp_filepath_tmpl = os.path.join(download_dir, f"{filename}.%(ext)s")
128
+
129
  # 設置 yt-dlp 選項
130
  ydl_opts = {
131
  'format': 'bestaudio/best',
 
139
  }],
140
  'ffmpeg_location': shutil.which("ffmpeg"),
141
  }
142
+
143
  # 檢查 ffmpeg
144
  if not ydl_opts['ffmpeg_location']:
145
  print("Warning: ffmpeg not found... / 警告:找不到 ffmpeg...")
146
+
147
  # 如果已經有 API 信息,使用它
148
  duration = api_info["duration"] if api_info else None
149
  title = api_info["title"] if api_info else "Unknown"
150
+
151
  # 下載音頻
152
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
153
  # 如果沒有 API 信息,從 yt-dlp 獲取
 
158
  else:
159
  # 有 API 信息,直接下載
160
  ydl.download([f"https://www.youtube.com/watch?v={video_id}"])
161
+
162
  # 確定最終文件路徑
163
  final_filepath = os.path.join(download_dir, f"{filename}.mp3")
164
+
165
  # 檢查文件是否存在
166
  if os.path.exists(final_filepath):
167
  print(f"YouTube audio downloaded: {final_filepath}")
 
170
  else:
171
  # 嘗試查找可能的文件
172
  potential_files = [
173
+ os.path.join(download_dir, f)
174
+ for f in os.listdir(download_dir)
175
  if f.startswith(filename) and f.endswith(".mp3")
176
  ]
177
  if potential_files:
 
180
  return downloaded_path, temp_dir, duration
181
  else:
182
  raise FileNotFoundError(f"Audio file not found after download in {download_dir}")
183
+
184
  except Exception as e:
185
  print(f"Error downloading YouTube audio: {e}")
186
  if temp_dir and os.path.exists(temp_dir):
 
190
  print(f"Error cleaning temp directory {temp_dir}: {cleanup_e}")
191
  return None, None, None
192
 
193
+ def process_youtube_url(youtube_url, user_api_key=None):
194
  """處理 YouTube URL,獲取信息並下載音頻
195
+
196
  Args:
197
  youtube_url: YouTube 視頻 URL
198
+ user_api_key: 用戶提供的 API 金鑰(可選)
199
+
200
  Returns:
201
  tuple: (音頻文件路徑, 視頻信息)
202
  """
203
  # 檢查 URL 是否有效
204
  if not youtube_url or not youtube_url.strip():
205
  return None, None
206
+
207
+ # 檢查是否在 Hugging Face Spaces 環境中
208
+ is_spaces = os.environ.get("SPACE_ID") is not None
209
+
210
+ # 如果提供了用戶 API 金鑰,設置它
211
+ if user_api_key:
212
+ set_api_key(user_api_key)
213
+
214
  # 提取視頻 ID
215
  video_id = extract_video_id(youtube_url)
216
  if not video_id:
217
  print(f"Invalid YouTube URL: {youtube_url}")
218
  return None, None
219
+
220
  # 檢查是否設置了 API 金鑰
221
  if YOUTUBE_API_KEY:
222
  # 使用 API 獲取視頻信息
223
  video_info = get_video_info(video_id)
224
  if not video_info:
225
  print(f"Could not get video info from API for: {video_id}")
226
+
227
+ # 如果在 Spaces 環境中且沒有 API 信息,則不嘗試下載
228
+ if is_spaces:
229
+ raise ValueError("YouTube 下載在 Hugging Face Spaces 中需要有效的 API 金鑰。")
230
+
231
  # 如果 API 失敗,嘗試直接下載
232
  audio_path, temp_dir, duration = download_audio(video_id)
233
  return audio_path, {"title": "Unknown", "duration": duration}
234
+
235
  # 使用 API 信息下載音頻
236
  audio_path, temp_dir, _ = download_audio(video_id, video_info)
237
  return audio_path, video_info
238
  else:
239
+ # 沒有 API 金鑰
240
+ if is_spaces:
241
+ # 在 Spaces 環境中需要 API 金鑰
242
+ raise ValueError("YouTube 下載在 Hugging Face Spaces 中需要 API 金鑰。請在上方的 'YouTube API Key Settings' 中輸入您的 API 金鑰。\n\nYouTube download in Hugging Face Spaces requires an API key. Please enter your API key in the 'YouTube API Key Settings' section above.")
243
+
244
+ # 本地環境,直接使用 yt-dlp
245
  print("No YouTube API key set, using yt-dlp directly")
246
  audio_path, temp_dir, duration = download_audio(video_id)
247
  return audio_path, {"title": "Unknown", "duration": duration}
 
251
  # 設置 API 金鑰(實際使用時應從環境變量或配置文件獲取)
252
  api_key = "YOUR_API_KEY"
253
  set_api_key(api_key)
254
+
255
  # 測試 URL
256
  test_url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
257
+
258
  # 處理 URL
259
  audio_path, video_info = process_youtube_url(test_url)
260
+
261
  if audio_path and video_info:
262
  print(f"Downloaded: {audio_path}")
263
  print(f"Video info: {video_info}")