adding publish video node

This commit is contained in:
dseditor
2025-06-17 09:53:28 +08:00
parent 5fd252dc84
commit 57fae07047
2 changed files with 230 additions and 2 deletions
+229 -1
View File
@@ -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
View File
@@ -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"]