Spaces:
Running
Running
Update watcher.py
Browse files- watcher.py +113 -55
watcher.py
CHANGED
|
@@ -63,6 +63,51 @@ def extract_youtube_id(text):
|
|
| 63 |
return m.group(1)
|
| 64 |
return None
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
def get_video_title(youtube_url):
|
| 67 |
"""YouTube動画のタイトルを取得する簡易的な関数"""
|
| 68 |
try:
|
|
@@ -138,6 +183,39 @@ def select_best_video_and_audio(items):
|
|
| 138 |
|
| 139 |
return best_video, best_audio
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
# ===== ssyoutube サーバー側結合 =====
|
| 142 |
def merge_video_on_server(video_url, audio_url, video_id, quality, video_title, nonce):
|
| 143 |
"""
|
|
@@ -295,39 +373,29 @@ def main():
|
|
| 295 |
time.sleep(10)
|
| 296 |
continue
|
| 297 |
|
| 298 |
-
|
| 299 |
-
if not
|
| 300 |
time.sleep(10)
|
| 301 |
continue
|
| 302 |
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
|
|
|
|
|
|
| 306 |
video_title = get_video_title(youtube_url)
|
| 307 |
-
|
| 308 |
items, nonce = fetch_download_links(youtube_url)
|
| 309 |
-
|
| 310 |
-
if not nonce:
|
| 311 |
-
send_to_channel("nonceの取得に失敗しました。Izuemon(MOCA)を呼んでください。")
|
| 312 |
-
time.sleep(10)
|
| 313 |
-
continue
|
| 314 |
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
video_items.append(item)
|
| 326 |
-
|
| 327 |
-
# 最高画質の動画と音声を選択
|
| 328 |
-
best_video, best_audio = select_best_video_and_audio(items)
|
| 329 |
-
|
| 330 |
-
# メッセージを構築
|
| 331 |
message_lines = []
|
| 332 |
message_lines.append(f"<b>{video_title}</b>\n")
|
| 333 |
message_lines.append("<b>ダウンロードリンク</b>")
|
|
@@ -347,40 +415,30 @@ def main():
|
|
| 347 |
message_lines.append("自動で音声と動画を結合しています。\n★動画ファイルと音声ファイルをダウンロードして<link type=\"url\" value=\"https://qooly.com/ja/merge-video-and-audio-online\">ここ</link>などで結合すると早くて確実です。")
|
| 348 |
send_to_channel("\n".join(message_lines))
|
| 349 |
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
if quality_match:
|
| 358 |
-
quality = quality_match.group(1)
|
| 359 |
-
|
| 360 |
-
# サーバー側で結合
|
| 361 |
-
print(f"サーバー側での結合を開始します: {youtube_id} {quality}p")
|
| 362 |
try:
|
| 363 |
merged_url = merge_video_on_server(
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
video_title,
|
| 369 |
nonce
|
| 370 |
)
|
| 371 |
-
|
| 372 |
except Exception as e:
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
# 処理済みとしてマーク
|
| 381 |
-
processed_messages.add(latest_msg.get("id"))
|
| 382 |
-
|
| 383 |
-
print(f"送信完了: {youtube_id}")
|
| 384 |
|
| 385 |
except Exception as e:
|
| 386 |
print("エラー:", e)
|
|
|
|
| 63 |
return m.group(1)
|
| 64 |
return None
|
| 65 |
|
| 66 |
+
def extract_youtube_and_option(text):
|
| 67 |
+
"""
|
| 68 |
+
YouTube URL と画質指定オプションを抽出する
|
| 69 |
+
戻り値: (video_id, option) 例: ("abcdEFGHijk", "720")
|
| 70 |
+
"""
|
| 71 |
+
# 全角スペースも対象に
|
| 72 |
+
parts = re.split(r"[ \u3000]+", text.strip())
|
| 73 |
+
|
| 74 |
+
video_id = extract_youtube_id(parts[0])
|
| 75 |
+
option = None
|
| 76 |
+
if len(parts) > 1:
|
| 77 |
+
option = parts[1].lower()
|
| 78 |
+
|
| 79 |
+
return video_id, option
|
| 80 |
+
|
| 81 |
+
def normalize_quality(option):
|
| 82 |
+
"""
|
| 83 |
+
★ ユーザー入力を内部品質コードへ変換
|
| 84 |
+
"""
|
| 85 |
+
if option is None:
|
| 86 |
+
return "720"
|
| 87 |
+
|
| 88 |
+
quality_map = {
|
| 89 |
+
"144": "144",
|
| 90 |
+
"240": "240",
|
| 91 |
+
"360": "360",
|
| 92 |
+
"480": "480",
|
| 93 |
+
"720": "720",
|
| 94 |
+
"1080": "1080",
|
| 95 |
+
"2k": "1440",
|
| 96 |
+
"1440": "1440",
|
| 97 |
+
"4k": "2160",
|
| 98 |
+
"2160": "2160",
|
| 99 |
+
"8k": "4320",
|
| 100 |
+
"4320": "4320",
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
if option in quality_map:
|
| 104 |
+
return quality_map[option]
|
| 105 |
+
|
| 106 |
+
if option in ["a", "f"]:
|
| 107 |
+
return option # 特殊指定
|
| 108 |
+
|
| 109 |
+
return "720" # デフォルト
|
| 110 |
+
|
| 111 |
def get_video_title(youtube_url):
|
| 112 |
"""YouTube動画のタイトルを取得する簡易的な関数"""
|
| 113 |
try:
|
|
|
|
| 183 |
|
| 184 |
return best_video, best_audio
|
| 185 |
|
| 186 |
+
def select_quality_video(items, quality):
|
| 187 |
+
"""
|
| 188 |
+
★ 指定画質に最適な動画を選択(音声なし映像のみ)
|
| 189 |
+
"""
|
| 190 |
+
video_candidates = []
|
| 191 |
+
for item in items:
|
| 192 |
+
if item["has_audio"] == "false":
|
| 193 |
+
m = re.match(r"(\d+)p", item["quality"])
|
| 194 |
+
if m:
|
| 195 |
+
q = int(m.group(1))
|
| 196 |
+
video_candidates.append((q, item))
|
| 197 |
+
|
| 198 |
+
if not video_candidates:
|
| 199 |
+
return None
|
| 200 |
+
|
| 201 |
+
# 最大画質
|
| 202 |
+
video_candidates.sort(key=lambda x: x[0])
|
| 203 |
+
|
| 204 |
+
if quality == "f": # 最大画質
|
| 205 |
+
return video_candidates[-1][1]
|
| 206 |
+
|
| 207 |
+
req = int(quality)
|
| 208 |
+
best = None
|
| 209 |
+
|
| 210 |
+
# 指定画質以上で一番近いもの
|
| 211 |
+
for q, item in video_candidates:
|
| 212 |
+
if q >= req:
|
| 213 |
+
best = item
|
| 214 |
+
break
|
| 215 |
+
|
| 216 |
+
# 無ければ最大画質
|
| 217 |
+
return best if best else video_candidates[-1][1]
|
| 218 |
+
|
| 219 |
# ===== ssyoutube サーバー側結合 =====
|
| 220 |
def merge_video_on_server(video_url, audio_url, video_id, quality, video_title, nonce):
|
| 221 |
"""
|
|
|
|
| 373 |
time.sleep(10)
|
| 374 |
continue
|
| 375 |
|
| 376 |
+
video_id, option = extract_youtube_and_option(latest_msg["plainText"])
|
| 377 |
+
if not video_id:
|
| 378 |
time.sleep(10)
|
| 379 |
continue
|
| 380 |
|
| 381 |
+
quality_opt = normalize_quality(option)
|
| 382 |
+
|
| 383 |
+
youtube_url = f"https://www.youtube.com/watch?v={video_id}"
|
| 384 |
+
send_to_channel(f"{video_id} のダウンロードを開始します。")
|
| 385 |
+
|
| 386 |
video_title = get_video_title(youtube_url)
|
|
|
|
| 387 |
items, nonce = fetch_download_links(youtube_url)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
|
| 389 |
+
all_items = items[:]
|
| 390 |
+
audio_items = [i for i in items if i["quality"] == "audio"]
|
| 391 |
+
|
| 392 |
+
selected_video = None
|
| 393 |
+
selected_audio = audio_items[0] if audio_items else None
|
| 394 |
+
|
| 395 |
+
if quality_opt not in ["a"]:
|
| 396 |
+
selected_video = select_quality_video(items, quality_opt)
|
| 397 |
+
|
| 398 |
+
# ダウンロードリスト送信
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
message_lines = []
|
| 400 |
message_lines.append(f"<b>{video_title}</b>\n")
|
| 401 |
message_lines.append("<b>ダウンロードリンク</b>")
|
|
|
|
| 415 |
message_lines.append("自動で音声と動画を結合しています。\n★動画ファイルと音声ファイルをダウンロードして<link type=\"url\" value=\"https://qooly.com/ja/merge-video-and-audio-online\">ここ</link>などで結合すると早くて確実です。")
|
| 416 |
send_to_channel("\n".join(message_lines))
|
| 417 |
|
| 418 |
+
# ★ 結合フラグ
|
| 419 |
+
do_merge = quality_opt not in ["a"]
|
| 420 |
+
|
| 421 |
+
if do_merge and selected_video and selected_audio:
|
| 422 |
+
m = re.match(r"(\d+)p", selected_video["quality"])
|
| 423 |
+
if m:
|
| 424 |
+
q = m.group(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
try:
|
| 426 |
merged_url = merge_video_on_server(
|
| 427 |
+
selected_video["url"],
|
| 428 |
+
selected_audio["url"],
|
| 429 |
+
video_id,
|
| 430 |
+
q,
|
| 431 |
video_title,
|
| 432 |
nonce
|
| 433 |
)
|
| 434 |
+
send_to_channel(f"<link type=\"url\" value=\"{merged_url}\">{q}p 結合済み動画</link>")
|
| 435 |
except Exception as e:
|
| 436 |
+
send_to_channel(f"結合失敗: {e}")
|
| 437 |
+
|
| 438 |
+
send_to_channel("完了しました!")
|
| 439 |
+
processed_messages.add(latest_msg["id"])
|
| 440 |
+
|
| 441 |
+
print(f"送信完了: {video_id}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 442 |
|
| 443 |
except Exception as e:
|
| 444 |
print("エラー:", e)
|