rein0421 commited on
Commit
699fc01
·
verified ·
1 Parent(s): b6446ec

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +69 -30
app.py CHANGED
@@ -45,18 +45,47 @@ TEMP_DIR = '/tmp'
45
 
46
  # --- バックエンド処理関数 ---
47
 
 
 
48
  def download_and_extract_audio(youtube_url):
49
  """
50
  yt-dlpを使って動画をダウンロードし、音声をMP3形式で抽出する。
51
- 成功したら (音声ファイルパス, 動画情報) のタプルを、失敗したら (None, None) を返す。
52
  """
53
- # <<< 修正 >>> 一時ディレクトリを使用
54
  output_dir = os.path.join(TEMP_DIR, 'downloads')
55
  os.makedirs(output_dir, exist_ok=True)
56
  logging.debug(f"音声保存ディレクトリ: {os.path.abspath(output_dir)}")
57
 
58
  output_template = os.path.join(output_dir, '%(id)s.%(ext)s')
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  ydl_opts = {
61
  'format': 'bestaudio/best',
62
  'postprocessors': [{
@@ -67,7 +96,11 @@ def download_and_extract_audio(youtube_url):
67
  'outtmpl': output_template,
68
  'noplaylist': True,
69
  'logger': logging.getLogger('yt_dlp'),
70
- 'verbose': False,
 
 
 
 
71
  }
72
 
73
  audio_file_path = None
@@ -75,65 +108,71 @@ def download_and_extract_audio(youtube_url):
75
 
76
  try:
77
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
78
- logging.info(f"yt-dlp: {youtube_url} のダウンロードと音声抽出を開始")
79
  info_dict = ydl.extract_info(youtube_url, download=True)
 
 
80
  video_id = info_dict.get('id', 'unknown_id')
81
- # 動画情報も返すようにする
82
  info_dict_result = {
83
  'id': video_id,
84
  'title': info_dict.get('title', f'動画 {video_id}'),
85
- 'thumbnail': info_dict.get('thumbnail'), # サムネイルURL
86
  'uploader': info_dict.get('uploader'),
87
  'duration': info_dict.get('duration'),
88
  }
89
  logging.info(f"yt-dlp: 動画情報取得完了 (ID: {video_id}, Title: {info_dict_result['title']})")
90
 
91
  base_filename = ydl.prepare_filename(info_dict)
92
- # yt-dlp の prepare_filename は絶対パスを返すことがあるので、
93
- # 確実に /tmp 配下のパスにするため、ファイル名だけ取得して結合する
94
- # expected_mp3_path = os.path.splitext(base_filename)[0] + '.mp3'
95
  expected_mp3_filename = os.path.splitext(os.path.basename(base_filename))[0] + '.mp3'
96
  expected_mp3_path = os.path.join(output_dir, expected_mp3_filename)
97
  logging.debug(f"yt-dlp: 期待されるMP3ファイルパス -> {expected_mp3_path}")
98
 
99
-
100
  wait_time = 0
101
- max_wait = 15 # 少し長めに待つ (15秒) / 環境によっては時間がかかる場合あり
102
  while not os.path.exists(expected_mp3_path) and wait_time < max_wait:
103
  logging.debug(f"MP3ファイル待機中: {expected_mp3_path} (待機時間: {wait_time}秒)")
104
  time.sleep(1)
105
  wait_time += 1
106
 
107
  if os.path.exists(expected_mp3_path):
108
- audio_file_path = expected_mp3_path
109
- logging.info(f"yt-dlp: 音声抽出完了 -> {audio_file_path}")
110
- return audio_file_path, info_dict_result
111
  else:
112
- logging.warning(f"期待されたMP3ファイルが見つかりません: {expected_mp3_path}")
113
- # output_dir 内のファイルをリストアップして確認
114
- potential_files = [f for f in os.listdir(output_dir) if f.startswith(video_id) and f.endswith('.mp3')]
115
- if potential_files:
116
- # output_dirを基準に絶対パスを生成
117
- audio_file_path = os.path.join(output_dir, potential_files[0])
118
- logging.info(f"yt-dlp: 代替検索で見つかった音声ファイル -> {audio_file_path}")
119
- return audio_file_path, info_dict_result
120
- else:
121
  logging.error("yt-dlp: 音声抽出後のMP3ファイル特定に失敗しました。")
122
  logging.error(f"Downloads directory ({output_dir}) contents: {os.listdir(output_dir)}")
123
- return None, info_dict_result # ファイルパスはNoneだが情報は返す
124
 
125
  except yt_dlp.utils.DownloadError as e:
126
  logging.error(f"yt-dlp ダウンロードエラー: {e}")
127
- if "confirm your age" in str(e).lower():
128
- logging.error("年齢確認が必要な動画の可能性があります。クッキーファイルの使用を検討してください。")
129
- elif "video unavailable" in str(e).lower():
130
- logging.error("動画が利用不可能なようです。")
131
- elif "Private video" in str(e):
132
- logging.error("非公開動画のようです。")
 
 
 
133
  return None, None
134
  except Exception as e:
135
  logging.error("yt-dlp: 音声抽出中に予期せぬエラーが発生しました。", exc_info=True)
136
  return None, None
 
 
 
 
 
 
 
 
137
 
138
  def transcribe_audio(audio_path):
139
  """
 
45
 
46
  # --- バックエンド処理関数 ---
47
 
48
+ TEMP_DIR = '/tmp' # Spacesで書き込み可能な一時ディレクトリ
49
+
50
  def download_and_extract_audio(youtube_url):
51
  """
52
  yt-dlpを使って動画をダウンロードし、音声をMP3形式で抽出する。
53
+ 環境変数から読み込んだCookieを使用する。
54
  """
 
55
  output_dir = os.path.join(TEMP_DIR, 'downloads')
56
  os.makedirs(output_dir, exist_ok=True)
57
  logging.debug(f"音声保存ディレクトリ: {os.path.abspath(output_dir)}")
58
 
59
  output_template = os.path.join(output_dir, '%(id)s.%(ext)s')
60
 
61
+ # --- Cookie処理 ---
62
+ cookie_secret_name = 'YOUTUBE_COOKIES' # 手順2で登録したSecret名
63
+ cookie_content = os.getenv(cookie_secret_name)
64
+ cookie_file_path = None
65
+ temp_cookie_file_handle = None # finallyで閉じるため
66
+
67
+ if cookie_content:
68
+ try:
69
+ # 一時ファイルとしてCookieを書き出す
70
+ # delete=False にして、yt-dlpが読み込めるようにファイルを保持する
71
+ temp_cookie_file_handle = tempfile.NamedTemporaryFile(
72
+ mode='w', encoding='utf-8', dir=TEMP_DIR, suffix='.txt', delete=False
73
+ )
74
+ temp_cookie_file_handle.write(cookie_content)
75
+ cookie_file_path = temp_cookie_file_handle.name
76
+ temp_cookie_file_handle.close() # 書き込み後に一度閉じる(yt-dlpがアクセスできるように)
77
+ logging.info(f"環境変数 '{cookie_secret_name}' からCookieを一時ファイル '{cookie_file_path}' に書き出しました。")
78
+ except Exception as e:
79
+ logging.error(f"一時Cookieファイルの書き出し中にエラー: {e}", exc_info=True)
80
+ cookie_file_path = None # エラー時はCookieを使用しない
81
+ if temp_cookie_file_handle:
82
+ try:
83
+ temp_cookie_file_handle.close() # エラー時も閉じる試み
84
+ except: pass
85
+ else:
86
+ logging.warning(f"環境変数 '{cookie_secret_name}' が未設定です。Cookieなしで試行します。")
87
+ # ---------------
88
+
89
  ydl_opts = {
90
  'format': 'bestaudio/best',
91
  'postprocessors': [{
 
96
  'outtmpl': output_template,
97
  'noplaylist': True,
98
  'logger': logging.getLogger('yt_dlp'),
99
+ 'verbose': False, # デバッグ時にTrueにすると詳細ログが出力される
100
+ # --- Cookieオプションを追加 ---
101
+ 'cookiefile': cookie_file_path, # yt-dlpにCookieファイルのパスを渡す
102
+ # --------------------------
103
+ # '--cookies-from-browser' はサーバー環境では使えないので注意
104
  }
105
 
106
  audio_file_path = None
 
108
 
109
  try:
110
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
111
+ logging.info(f"yt-dlp: {youtube_url} のダウンロードと音声抽出を開始 (Cookie使用: {'あり' if cookie_file_path else 'なし'})")
112
  info_dict = ydl.extract_info(youtube_url, download=True)
113
+
114
+ # ... (動画情報取得、ファイルパス特定、ファイル待機処理は変更なし) ...
115
  video_id = info_dict.get('id', 'unknown_id')
 
116
  info_dict_result = {
117
  'id': video_id,
118
  'title': info_dict.get('title', f'動画 {video_id}'),
119
+ 'thumbnail': info_dict.get('thumbnail'),
120
  'uploader': info_dict.get('uploader'),
121
  'duration': info_dict.get('duration'),
122
  }
123
  logging.info(f"yt-dlp: 動画情報取得完了 (ID: {video_id}, Title: {info_dict_result['title']})")
124
 
125
  base_filename = ydl.prepare_filename(info_dict)
 
 
 
126
  expected_mp3_filename = os.path.splitext(os.path.basename(base_filename))[0] + '.mp3'
127
  expected_mp3_path = os.path.join(output_dir, expected_mp3_filename)
128
  logging.debug(f"yt-dlp: 期待されるMP3ファイルパス -> {expected_mp3_path}")
129
 
 
130
  wait_time = 0
131
+ max_wait = 15
132
  while not os.path.exists(expected_mp3_path) and wait_time < max_wait:
133
  logging.debug(f"MP3ファイル待機中: {expected_mp3_path} (待機時間: {wait_time}秒)")
134
  time.sleep(1)
135
  wait_time += 1
136
 
137
  if os.path.exists(expected_mp3_path):
138
+ audio_file_path = expected_mp3_path
139
+ logging.info(f"yt-dlp: 音声抽出完了 -> {audio_file_path}")
140
+ return audio_file_path, info_dict_result
141
  else:
142
+ logging.warning(f"期待されたMP3ファイルが見つかりません: {expected_mp3_path}")
143
+ potential_files = [f for f in os.listdir(output_dir) if f.startswith(video_id) and f.endswith('.mp3')]
144
+ if potential_files:
145
+ audio_file_path = os.path.join(output_dir, potential_files[0])
146
+ logging.info(f"yt-dlp: 代替検索で見つかった音声ファイル -> {audio_file_path}")
147
+ return audio_file_path, info_dict_result
148
+ else:
 
 
149
  logging.error("yt-dlp: 音声抽出後のMP3ファイル特定に失敗しました。")
150
  logging.error(f"Downloads directory ({output_dir}) contents: {os.listdir(output_dir)}")
151
+ return None, info_dict_result
152
 
153
  except yt_dlp.utils.DownloadError as e:
154
  logging.error(f"yt-dlp ダウンロードエラー: {e}")
155
+ err_str = str(e).lower()
156
+ # エラーメッセージに基づいて、より具体的なログを出力
157
+ if "sign in to confirm" in err_str or "confirm your age" in err_str:
158
+ logging.error("ボット確認/年齢確認エラー。提供されたCookieが無効か期限切れの可能性があります。再度Cookieを取得してSecretを更新してください。")
159
+ elif "video unavailable" in err_str:
160
+ logging.error("動画が利用不可です。URLが正しいか確認してください。")
161
+ elif "private video" in err_str:
162
+ logging.error("非公開動画です。Cookieがその動画へのアクセス権を持っているか確認してください。")
163
+ # 他にも 'livestream'、'members-only' などのエラーがありうる
164
  return None, None
165
  except Exception as e:
166
  logging.error("yt-dlp: 音声抽出中に予期せぬエラーが発生しました。", exc_info=True)
167
  return None, None
168
+ finally:
169
+ # --- 一時Cookieファイルを削除 ---
170
+ if cookie_file_path and os.path.exists(cookie_file_path):
171
+ try:
172
+ os.remove(cookie_file_path)
173
+ logging.info(f"一時Cookieファイル {cookie_file_path} を削除しました。")
174
+ except OSError as rm_err:
175
+ logging.error(f"一時Cookieファイル {cookie_file_path} の削除に失敗: {rm_err}")
176
 
177
  def transcribe_audio(audio_path):
178
  """