You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RedditVideoMakerBot/scheduler/pipeline.py

237 lines
8.3 KiB

"""
Scheduler - Hệ thống lên lịch tự động tạo và đăng video.
Sử dụng APScheduler để lên lịch các tác vụ tự động.
"""
import math
import os
import sys
from datetime import datetime
from os import name
from pathlib import Path
from subprocess import Popen
from typing import Optional
from utils import settings
from utils.cleanup import cleanup
from utils.console import print_markdown, print_step, print_substep
from utils.id import extract_id
from utils.title_history import save_title
def run_pipeline(post_id: Optional[str] = None) -> Optional[str]:
"""Chạy toàn bộ pipeline tạo video từ Threads.
Pipeline:
1. Lấy nội dung từ Threads
2. Tạo TTS audio
3. Tạo screenshots
4. Tải background video/audio
5. Ghép video cuối cùng
6. Upload lên các platform (nếu được cấu hình)
Args:
post_id: ID cụ thể của thread (optional).
Returns:
Đường dẫn file video đã tạo, hoặc None nếu thất bại.
"""
from threads.threads_client import get_threads_posts
from video_creation.background import (
chop_background,
download_background_audio,
download_background_video,
get_background_config,
)
from video_creation.final_video import make_final_video
from video_creation.threads_screenshot import get_screenshots_of_threads_posts
from video_creation.voices import save_text_to_mp3
print_step("🚀 Bắt đầu pipeline tạo video...")
try:
# Step 1: Lấy nội dung từ Threads
print_step("📱 Bước 1: Lấy nội dung từ Threads...")
thread_object = get_threads_posts(post_id)
thread_id = extract_id(thread_object)
print_substep(f"Thread ID: {thread_id}", style="bold blue")
# Step 2: Tạo TTS audio
print_step("🎙️ Bước 2: Tạo audio TTS...")
length, number_of_comments = save_text_to_mp3(thread_object)
length = math.ceil(length)
# Step 3: Tạo screenshots
print_step("📸 Bước 3: Tạo hình ảnh...")
get_screenshots_of_threads_posts(thread_object, number_of_comments)
# Step 4: Background
print_step("🎬 Bước 4: Xử lý background...")
bg_config = {
"video": get_background_config("video"),
"audio": get_background_config("audio"),
}
download_background_video(bg_config["video"])
download_background_audio(bg_config["audio"])
chop_background(bg_config, length, thread_object)
# Step 5: Ghép video cuối cùng
print_step("🎥 Bước 5: Tạo video cuối cùng...")
make_final_video(number_of_comments, length, thread_object, bg_config)
# Tìm file video đã tạo
subreddit = (
settings.config.get("threads", {}).get("thread", {}).get("channel_name", "threads")
)
results_dir = f"./results/{subreddit}"
video_path = None
if os.path.exists(results_dir):
files = sorted(
[f for f in os.listdir(results_dir) if f.endswith(".mp4")],
key=lambda x: os.path.getmtime(os.path.join(results_dir, x)),
reverse=True,
)
if files:
video_path = os.path.join(results_dir, files[0])
# Step 6: Upload (nếu cấu hình)
upload_config = settings.config.get("uploaders", {})
has_uploaders = any(
upload_config.get(p, {}).get("enabled", False) for p in ["youtube", "tiktok", "facebook"]
)
if has_uploaders and video_path:
print_step("📤 Bước 6: Upload video lên các platform...")
from uploaders.upload_manager import UploadManager
manager = UploadManager()
title = thread_object.get("thread_title", "Threads Video")[:100]
description = thread_object.get("thread_post", "")[:500]
# Tìm thumbnail nếu có
thumbnail_path = None
thumb_candidate = f"./assets/temp/{thread_id}/thumbnail.png"
if os.path.exists(thumb_candidate):
thumbnail_path = thumb_candidate
results = manager.upload_to_all(
video_path=video_path,
title=title,
description=description,
thumbnail_path=thumbnail_path,
)
print_step("📊 Kết quả upload:")
for platform, url in results.items():
if url:
print_substep(f"{platform}: {url}", style="bold green")
else:
print_substep(f"{platform}: Thất bại", style="bold red")
print_step("✅ Pipeline hoàn tất!")
# Lưu title vào lịch sử để tránh tạo trùng lặp
title = thread_object.get("thread_title", "")
tid = thread_object.get("thread_id", "")
if title:
save_title(title=title, thread_id=tid, source="threads")
return video_path
except Exception as e:
print_substep(f"❌ Lỗi pipeline: {e}", style="bold red")
raise
def run_scheduled():
"""Chạy pipeline theo lịch trình đã cấu hình.
Sử dụng APScheduler để lên lịch.
"""
try:
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger
except ImportError:
print_substep(
"Cần cài đặt APScheduler: pip install apscheduler",
style="bold red",
)
return
scheduler_config = settings.config.get("scheduler", {})
enabled = scheduler_config.get("enabled", False)
if not enabled:
print_substep("Scheduler chưa được kích hoạt trong config!", style="bold yellow")
return
timezone = scheduler_config.get("timezone", "Asia/Ho_Chi_Minh")
cron_expression = scheduler_config.get(
"cron", "0 */3 * * *"
) # Mặc định mỗi 3 giờ (8 lần/ngày: 00, 03, 06, 09, 12, 15, 18, 21h)
max_videos_per_day = scheduler_config.get("max_videos_per_day", 8)
# Parse cron expression
cron_parts = cron_expression.split()
if len(cron_parts) != 5:
print_substep(
"Cron expression không hợp lệ! Format: minute hour day month weekday", style="bold red"
)
return
scheduler = BlockingScheduler(timezone=timezone)
videos_today = {"count": 0, "date": datetime.now().strftime("%Y-%m-%d")}
def scheduled_job():
"""Job được chạy theo lịch."""
current_date = datetime.now().strftime("%Y-%m-%d")
# Reset counter nếu sang ngày mới
if current_date != videos_today["date"]:
videos_today["count"] = 0
videos_today["date"] = current_date
if videos_today["count"] >= max_videos_per_day:
print_substep(
f"Đã đạt giới hạn {max_videos_per_day} video/ngày. Bỏ qua.",
style="bold yellow",
)
return
print_step(f"⏰ Scheduler: Bắt đầu tạo video lúc {datetime.now().strftime('%H:%M:%S')}...")
try:
result = run_pipeline()
if result:
videos_today["count"] += 1
print_substep(
f"Video #{videos_today['count']}/{max_videos_per_day} ngày hôm nay",
style="bold blue",
)
except Exception as e:
print_substep(f"Scheduler job thất bại: {e}", style="bold red")
trigger = CronTrigger(
minute=cron_parts[0],
hour=cron_parts[1],
day=cron_parts[2],
month=cron_parts[3],
day_of_week=cron_parts[4],
timezone=timezone,
)
scheduler.add_job(scheduled_job, trigger, id="video_pipeline", replace_existing=True)
print_step(f"📅 Scheduler đã khởi động!")
print_substep(f" Cron: {cron_expression}", style="bold blue")
print_substep(f" Timezone: {timezone}", style="bold blue")
print_substep(f" Max videos/ngày: {max_videos_per_day}", style="bold blue")
print_substep(" Nhấn Ctrl+C để dừng", style="bold yellow")
try:
scheduler.start()
except (KeyboardInterrupt, SystemExit):
scheduler.shutdown()
print_step("Scheduler đã dừng.")