izuemon commited on
Commit
265a509
·
verified ·
1 Parent(s): fe97e6f

Update watcher.py

Browse files
Files changed (1) hide show
  1. watcher.py +90 -64
watcher.py CHANGED
@@ -2,6 +2,7 @@ import os
2
  import re
3
  import time
4
  import json
 
5
  import requests
6
  from datetime import datetime, timezone
7
  from bs4 import BeautifulSoup
@@ -18,7 +19,7 @@ PARAMS = {
18
 
19
  X_ACCOUNT = os.getenv("channeliotokenbot2")
20
  if not X_ACCOUNT:
21
- raise RuntimeError("環境変数 channeliotokenbot2 が設定されていません")
22
 
23
  HEADERS_GET = {
24
  "accept": "application/json",
@@ -35,8 +36,11 @@ HEADERS_POST = {
35
 
36
  # ===== ssyoutube =====
37
  SSYOUTUBE_URL = "https://ssyoutube.online/yt-video-detail/"
38
- SSYOUTUBE_MERGE_URL = "https://ssyoutube.online/wp-admin/admin-ajax.php"
39
- SSYOUTUBE_NONCE = "82b3e4b0cd" # 必要に応じて変更
 
 
 
40
 
41
  # ===== Utils =====
42
  def parse_updated_at(value):
@@ -76,36 +80,78 @@ def fetch_download_links(youtube_url):
76
  results = []
77
  for btn in buttons:
78
  url = btn.get("data-url")
79
- quality = btn.get("data-quality") # 例: 1080p / 720p / None
80
- has_audio = btn.get("data-has-audio") # "true" / "false" / None
81
 
82
  if not url:
83
  continue
84
 
85
  results.append({
86
  "url": url,
87
- "quality": quality or "audio",
88
  "has_audio": has_audio,
89
  })
90
 
91
  return results
92
 
93
- def build_links(items):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  lines = []
95
  for item in items:
96
  url = item["url"]
97
  quality = item["quality"]
98
- has_audio = item["has_audio"]
99
-
100
- audio_label = ""
101
- if has_audio == "false":
102
- audio_label = "(映像のみ)"
103
- elif has_audio == "true":
104
- audio_label = "(音声付き)"
105
-
106
- line = f'<link type="url" value="{url}"> {quality} {audio_label}</link>'
107
  lines.append(line)
108
 
 
109
  return "\n".join(lines)
110
 
111
  def send_to_channel(text):
@@ -132,43 +178,6 @@ def send_to_channel(text):
132
  )
133
  res.raise_for_status()
134
 
135
- # ===== 動画 + 音声結合 =====
136
- def merge_video_audio(video_url, audio_url, output_name):
137
- payload = {
138
- "action": "process_video_merge",
139
- "nonce": SSYOUTUBE_NONCE,
140
- "request_data": json.dumps({
141
- "id": f"{output_name}_merge",
142
- "ttl": 3600000,
143
- "inputs": [
144
- {"url": audio_url, "ext": "mp4"},
145
- {"url": video_url, "ext": "mp4"}
146
- ],
147
- "output": {
148
- "ext": "mp4",
149
- "downloadName": f"{output_name}.mp4",
150
- "chunkUpload": {"size": 104857600, "concurrency": 3}
151
- },
152
- "operation": {"type": "replace_audio_in_video"}
153
- })
154
- }
155
-
156
- res = requests.post(
157
- SSYOUTUBE_MERGE_URL,
158
- data=payload,
159
- headers={
160
- "User-Agent": "Mozilla/5.0",
161
- "Referer": "https://ssyoutube.online/yt-video-detail/"
162
- },
163
- timeout=30
164
- )
165
- res.raise_for_status()
166
- result = res.json().get("data", {}).get("result", {})
167
- monitor_url = result.get("monitor", {}).get("http")
168
- if not monitor_url:
169
- raise RuntimeError("動画結合 API が返す monitor URL が取得できませんでした")
170
- return monitor_url
171
-
172
  # ===== Main =====
173
  def main():
174
  while True:
@@ -214,22 +223,39 @@ def main():
214
 
215
  items = fetch_download_links(youtube_url)
216
  if not items:
217
- print("ダウンロードリンクが取得できませんでした")
218
  time.sleep(10)
219
  continue
220
 
221
- # 映像のみと音声のみを取得
222
- video_items = [i for i in items if i["has_audio"] == "false"]
223
- audio_items = [i for i in items if i["has_audio"] == "true" and i["quality"] == "audio"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
- if video_items and audio_items:
226
- # 最も高画質な動画
227
- video_item = sorted(video_items, key=lambda x: int(re.sub(r"\D", "", x["quality"] or "0")), reverse=True)[0]
228
- audio_item = audio_items[0]
229
- merged_url = merge_video_audio(video_item["url"], audio_item["url"], f"yt_{youtube_id}")
230
- items.append({"url": merged_url, "quality": "merged", "has_audio": "true"})
231
 
232
- message_text = build_links(items)
233
  send_to_channel(message_text)
234
  print("送信完了")
235
 
 
2
  import re
3
  import time
4
  import json
5
+ import subprocess
6
  import requests
7
  from datetime import datetime, timezone
8
  from bs4 import BeautifulSoup
 
19
 
20
  X_ACCOUNT = os.getenv("channeliotokenbot2")
21
  if not X_ACCOUNT:
22
+ raise RuntimeError("環境変数 channeliotokenokenbot2 が設定されていません")
23
 
24
  HEADERS_GET = {
25
  "accept": "application/json",
 
36
 
37
  # ===== ssyoutube =====
38
  SSYOUTUBE_URL = "https://ssyoutube.online/yt-video-detail/"
39
+
40
+ # ===== tfLink クライアント =====
41
+ from tflink import TFLinkClient
42
+
43
+ tf_client = TFLinkClient() # 匿名アップロード
44
 
45
  # ===== Utils =====
46
  def parse_updated_at(value):
 
80
  results = []
81
  for btn in buttons:
82
  url = btn.get("data-url")
83
+ quality = btn.get("data-quality")
84
+ has_audio = btn.get("data-has-audio")
85
 
86
  if not url:
87
  continue
88
 
89
  results.append({
90
  "url": url,
91
+ "quality": quality,
92
  "has_audio": has_audio,
93
  })
94
 
95
  return results
96
 
97
+ # ===== 動画と音声の選別 =====
98
+ def choose_best_streams(items):
99
+ # 映像 + 音声付きは優先しない
100
+ video_only = []
101
+ audio_only = []
102
+
103
+ for item in items:
104
+ if item["has_audio"] == "false":
105
+ # 映像のみ
106
+ video_only.append(item)
107
+ elif item["has_audio"] == "true":
108
+ # 音声付き(どちらでも使える)
109
+ # 映像付きか音声のみか
110
+ if item["quality"] and "p" in item["quality"]:
111
+ # これは映像 + 音声付き
112
+ video_only.append(item)
113
+ else:
114
+ audio_only.append(item)
115
+
116
+ # 映像は最高画質
117
+ video = sorted(video_only,
118
+ key=lambda x: int(re.sub(r"[^\d]", "", x["quality"] or "0")),
119
+ reverse=True)
120
+ audio = sorted(audio_only,
121
+ key=lambda x: int(re.sub(r"[^\d]", "", x["quality"] or "0")),
122
+ reverse=True)
123
+
124
+ return (video[0] if video else None, audio[0] if audio else None)
125
+
126
+ # ===== 結合 =====
127
+ def merge_video_audio(video_url, audio_url, out_file):
128
+ cmd = [
129
+ "ffmpeg", "-y",
130
+ "-i", video_url,
131
+ "-i", audio_url,
132
+ "-c", "copy",
133
+ out_file,
134
+ ]
135
+ result = subprocess.run(cmd, capture_output=True)
136
+ if result.returncode != 0:
137
+ print("FFmpeg merge error:", result.stderr.decode())
138
+ return False
139
+ return True
140
+
141
+ # ===== tfLink アップロード =====
142
+ def upload_to_tflink(file_path):
143
+ res = tf_client.upload(file_path)
144
+ return res.download_link
145
+
146
+ def build_links(items, upload_link):
147
  lines = []
148
  for item in items:
149
  url = item["url"]
150
  quality = item["quality"]
151
+ line = f'<link type="url" value="{url}"> {quality}</link>'
 
 
 
 
 
 
 
 
152
  lines.append(line)
153
 
154
+ lines.append(f"結合ファイルダウンロード: {upload_link}")
155
  return "\n".join(lines)
156
 
157
  def send_to_channel(text):
 
178
  )
179
  res.raise_for_status()
180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  # ===== Main =====
182
  def main():
183
  while True:
 
223
 
224
  items = fetch_download_links(youtube_url)
225
  if not items:
 
226
  time.sleep(10)
227
  continue
228
 
229
+ video_stream, audio_stream = choose_best_streams(items)
230
+ if not video_stream or not audio_stream:
231
+ print("映像または音声ストリームが足りません")
232
+ time.sleep(10)
233
+ continue
234
+
235
+ # ダウンロードして結合
236
+ temp_video = "video.mp4"
237
+ temp_audio = "audio.mp4"
238
+ merged = "merged_output.mp4"
239
+
240
+ # ダウンロード
241
+ with requests.get(video_stream["url"], stream=True) as r:
242
+ with open(temp_video, "wb") as f:
243
+ for chunk in r.iter_content(chunk_size=8192):
244
+ f.write(chunk)
245
+ with requests.get(audio_stream["url"], stream=True) as r:
246
+ with open(temp_audio, "wb") as f:
247
+ for chunk in r.iter_content(chunk_size=8192):
248
+ f.write(chunk)
249
+
250
+ if not merge_video_audio(temp_video, temp_audio, merged):
251
+ print("結合失敗")
252
+ time.sleep(10)
253
+ continue
254
 
255
+ # tfLink アップロード
256
+ upload_link = upload_to_tflink(merged)
 
 
 
 
257
 
258
+ message_text = build_links(items, upload_link)
259
  send_to_channel(message_text)
260
  print("送信完了")
261