adding publish video node
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
@@ -545,16 +546,243 @@ class PublishThread:
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
class ThreadPublishVideo:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True, "default": ""}),
|
||||
"video_path": ("STRING", {"multiline": False, "default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"ComfyUIHttpsURL": ("STRING", {"multiline": False, "default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("result",)
|
||||
FUNCTION = "publish_video"
|
||||
CATEGORY = "ComfyUI-Thread"
|
||||
|
||||
def publish_video(self, text, video_path, ComfyUIHttpsURL=""):
|
||||
try:
|
||||
# 讀取配置
|
||||
if not os.path.exists(CONFIG_FILE):
|
||||
return ("錯誤: 請先執行 StartWithLongLiveToken 節點",)
|
||||
|
||||
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
|
||||
threads_api = ThreadsAPI(
|
||||
config["USER_ID"],
|
||||
config["ACCESS_TOKEN"],
|
||||
config["APP_SECRET"]
|
||||
)
|
||||
|
||||
# 處理 ComfyUI URL 配置
|
||||
base_url = load_base_url()
|
||||
if ComfyUIHttpsURL.strip():
|
||||
# 更新 base URL
|
||||
base_url = ComfyUIHttpsURL.strip()
|
||||
save_base_url(base_url)
|
||||
print(f"更新基礎網址為: {base_url}")
|
||||
|
||||
print(f"使用基礎網址: {base_url}")
|
||||
|
||||
# 檢查是否提供了視頻路徑/網址
|
||||
if not video_path.strip():
|
||||
return ("錯誤: 請提供視頻路徑或網址",)
|
||||
|
||||
# 自動判斷是本地路徑還是網路網址
|
||||
video_path_clean = video_path.strip()
|
||||
final_video_url = ""
|
||||
|
||||
if self._is_url(video_path_clean):
|
||||
# 網路網址
|
||||
print(f"檢測到網路網址: {video_path_clean}")
|
||||
final_video_url = video_path_clean
|
||||
else:
|
||||
# 本地路徑
|
||||
print(f"檢測到本地路徑: {video_path_clean}")
|
||||
final_video_url = self._process_local_video(video_path_clean, base_url)
|
||||
if not final_video_url:
|
||||
return ("錯誤: 無法處理本地視頻文件",)
|
||||
|
||||
# 驗證最終的視頻 URL
|
||||
if not (final_video_url.startswith('http://') or final_video_url.startswith('https://')):
|
||||
return ("錯誤: 無法生成有效的視頻網址",)
|
||||
|
||||
print(f"開始處理視頻發布...")
|
||||
print(f"最終視頻網址: {final_video_url}")
|
||||
print(f"文字內容: {text}")
|
||||
|
||||
# 步驟 1: 創建視頻媒體容器
|
||||
print("正在創建視頻媒體容器...")
|
||||
media = threads_api.create_media_container(
|
||||
text=text,
|
||||
media_type="VIDEO",
|
||||
video_url=final_video_url
|
||||
)
|
||||
|
||||
media_id = media["id"]
|
||||
print(f"視頻媒體容器已創建,ID: {media_id}")
|
||||
|
||||
# 步驟 2: 檢查媒體容器狀態
|
||||
print("開始檢查媒體容器處理狀態...")
|
||||
max_attempts = 30 # 最多檢查 30 次 (10 分鐘)
|
||||
check_interval = 20 # 每 20 秒檢查一次
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
print(f"第 {attempt + 1} 次檢查媒體容器狀態...")
|
||||
|
||||
# 獲取媒體容器狀態
|
||||
status_response = self._check_media_status(threads_api, media_id, config["ACCESS_TOKEN"])
|
||||
|
||||
if status_response:
|
||||
status = status_response.get("status")
|
||||
print(f"媒體容器狀態: {status}")
|
||||
|
||||
if status == "FINISHED":
|
||||
print("媒體容器處理完成,準備發布...")
|
||||
break
|
||||
elif status == "ERROR":
|
||||
error_message = status_response.get("error_message", "未知錯誤")
|
||||
return (f"錯誤: 媒體容器處理失敗 - {error_message}",)
|
||||
elif status in ["IN_PROGRESS", "PUBLISHED"]:
|
||||
if attempt < max_attempts - 1:
|
||||
print(f"媒體容器仍在處理中,{check_interval} 秒後重新檢查...")
|
||||
time.sleep(check_interval)
|
||||
else:
|
||||
return ("錯誤: 媒體容器處理超時,請稍後再試",)
|
||||
else:
|
||||
print(f"未知狀態: {status},繼續等待...")
|
||||
if attempt < max_attempts - 1:
|
||||
time.sleep(check_interval)
|
||||
else:
|
||||
print("無法獲取媒體容器狀態,繼續等待...")
|
||||
if attempt < max_attempts - 1:
|
||||
time.sleep(check_interval)
|
||||
else:
|
||||
return ("錯誤: 媒體容器處理超時,請檢查視頻是否符合 Threads 規格要求",)
|
||||
|
||||
# 步驟 3: 發布視頻
|
||||
print("正在發布視頻...")
|
||||
result = threads_api.publish_container(media_id)
|
||||
|
||||
# 取得用戶名稱來組合網址
|
||||
user_info = threads_api.get_user_bio()
|
||||
username = user_info.get("username", "")
|
||||
thread_id = result["id"]
|
||||
|
||||
post_url = f"https://www.threads.net/@{username}/post/{thread_id}"
|
||||
|
||||
return (f"視頻發送成功!貼文網址: {post_url}",)
|
||||
|
||||
except Exception as e:
|
||||
print(f"視頻發布錯誤: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return (f"錯誤: {str(e)}",)
|
||||
|
||||
def _is_url(self, path):
|
||||
"""判斷輸入是否為網路網址"""
|
||||
return path.startswith('http://') or path.startswith('https://')
|
||||
|
||||
def _process_local_video(self, video_path, base_url):
|
||||
"""處理本地視頻文件,拷貝到 ComfyUI output 目錄並生成 URL"""
|
||||
try:
|
||||
import shutil
|
||||
|
||||
# 檢查本地文件是否存在
|
||||
if not os.path.exists(video_path):
|
||||
print(f"錯誤: 本地視頻文件不存在: {video_path}")
|
||||
return None
|
||||
|
||||
# 檢查是否為視頻文件
|
||||
video_extensions = ['.mp4', '.mov', '.avi', '.mkv', '.flv', '.wmv', '.webm']
|
||||
file_extension = os.path.splitext(video_path)[1].lower()
|
||||
|
||||
if file_extension not in video_extensions:
|
||||
print(f"警告: 檔案可能不是視頻格式: {file_extension}")
|
||||
|
||||
# 生成唯一文件名
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
import random
|
||||
random_id = random.randint(1000, 9999)
|
||||
original_filename = os.path.basename(video_path)
|
||||
name_without_ext = os.path.splitext(original_filename)[0]
|
||||
new_filename = f"thread_video_{timestamp}_{random_id}_{name_without_ext}{file_extension}"
|
||||
|
||||
# 獲取 ComfyUI output 目錄
|
||||
output_dir = get_output_directory()
|
||||
destination_path = os.path.join(output_dir, new_filename)
|
||||
|
||||
print(f"正在拷貝視頻文件...")
|
||||
print(f"來源: {video_path}")
|
||||
print(f"目標: {destination_path}")
|
||||
|
||||
# 拷貝文件到 output 目錄
|
||||
shutil.copy2(video_path, destination_path)
|
||||
|
||||
# 檢查拷貝是否成功
|
||||
if not os.path.exists(destination_path):
|
||||
print(f"錯誤: 文件拷貝失敗")
|
||||
return None
|
||||
|
||||
file_size = os.path.getsize(destination_path)
|
||||
print(f"視頻文件已拷貝成功,大小: {file_size / (1024*1024):.2f} MB")
|
||||
|
||||
# 檢查文件大小限制 (Threads 限制 1GB)
|
||||
max_size = 1024 * 1024 * 1024 # 1GB
|
||||
if file_size > max_size:
|
||||
print(f"警告: 文件大小 ({file_size / (1024*1024*1024):.2f} GB) 超過 Threads 限制 (1GB)")
|
||||
# 不刪除文件,讓用戶決定是否繼續
|
||||
|
||||
# 生成訪問 URL
|
||||
video_url = f"{base_url}/api/view?filename={new_filename}"
|
||||
print(f"生成視頻 URL: {video_url}")
|
||||
|
||||
return video_url
|
||||
|
||||
except Exception as e:
|
||||
print(f"處理本地視頻文件時發生錯誤: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def _check_media_status(self, threads_api, media_id, access_token):
|
||||
"""檢查媒體容器狀態"""
|
||||
try:
|
||||
# 使用 Threads API 檢查媒體容器狀態
|
||||
resp = requests.get(
|
||||
f"{threads_api.api_url}/{media_id}",
|
||||
params={
|
||||
"fields": "status,error_message",
|
||||
"access_token": access_token,
|
||||
},
|
||||
)
|
||||
|
||||
if resp.status_code == 200:
|
||||
return resp.json()
|
||||
else:
|
||||
print(f"狀態檢查 API 錯誤: {resp.status_code} - {resp.text}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"檢查媒體狀態時發生錯誤: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
# 註冊節點
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"StartWithLongLiveToken": StartWithLongLiveToken,
|
||||
"PublishThread": PublishThread,
|
||||
"ThreadsHistory": ThreadsHistory,
|
||||
"ThreadPublishVideo": ThreadPublishVideo,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"StartWithLongLiveToken": "Start With Long Live Token",
|
||||
"PublishThread": "Publish Thread",
|
||||
"ThreadsHistory": "Threads History",
|
||||
"ThreadPublishVideo": "Thread Publish Video",
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "thread"
|
||||
description = "A ComfyUI custom node package for seamless integration with Threads (Meta's social platform). This package allows you to publish posts, manage images, and retrieve post history directly from your ComfyUI workflows."
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = ["threads_sdk"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user