feat: telegram bot + normalize for mobile + nextcloud subcarpetas

This commit is contained in:
Renato
2026-07-02 17:28:39 +02:00
parent 5d8cfde527
commit d9310e904d
3 changed files with 226 additions and 1 deletions
+24 -1
View File
@@ -991,9 +991,25 @@ if __name__ == '__main__':
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
swears_path = "/home/ren/viral/brainrotinator/assets/swears.txt" swears_path = "/home/ren/viral/brainrotinator/assets/swears.txt"
nextcloud_dir = "/home/ren/nextcloud/html/data/renato97/files/viral_clips" nextcloud_dir = os.environ.get(
"NEXTCLOUD_TARGET",
"/home/ren/nextcloud/html/data/renato97/files/viral_clips"
)
os.makedirs(nextcloud_dir, exist_ok=True) os.makedirs(nextcloud_dir, exist_ok=True)
def normalize_for_mobile(path):
temp = path + ".tmp.mp4"
cmd = [
'ffmpeg', '-y', '-i', path,
'-c:v', 'libx264', '-preset', 'fast', '-crf', '23',
'-pix_fmt', 'yuv420p', '-profile:v', 'high', '-level:v', '4.1',
'-movflags', '+faststart',
'-c:a', 'aac', '-b:a', '128k',
temp
]
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
os.replace(temp, path)
def process_clip(i, clip): def process_clip(i, clip):
start = clip['start'] start = clip['start']
end = clip['end'] end = clip['end']
@@ -1057,6 +1073,13 @@ if __name__ == '__main__':
except Exception as e: except Exception as e:
print(f" ⚠️ [Clip {i+1}] Subtitle burn failed: {e}") print(f" ⚠️ [Clip {i+1}] Subtitle burn failed: {e}")
# Normalize for mobile
print(f" 📱 [Clip {i+1}] Normalizing for mobile playback...")
try:
normalize_for_mobile(clip_final_path)
except Exception as e:
print(f" ⚠️ [Clip {i+1}] Normalization failed: {e}")
# Copy to Nextcloud # Copy to Nextcloud
import shutil import shutil
nextcloud_dest = os.path.join(nextcloud_dir, clip_filename) nextcloud_dest = os.path.join(nextcloud_dir, clip_filename)
+185
View File
@@ -0,0 +1,185 @@
"""
Telegram bot for viral clip processing.
Receives YouTube URLs, processes via main.py, sends progress updates.
"""
import asyncio
import os
import re
import subprocess
import sys
import shutil
import tempfile
from datetime import datetime
from pathlib import Path
from dotenv import load_dotenv
from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
load_dotenv()
BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
CHAT_ID = int(os.environ["TELEGRAM_CHAT_ID"])
NEXTCLOUD_BASE = "/home/ren/nextcloud/html/data/renato97/files/viral_clips"
SCRIPT_DIR = Path(__file__).parent
queue = asyncio.Queue()
current_job: str | None = None
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
if update.effective_chat.id != CHAT_ID:
return
await update.message.reply_text(
"🎬 Mandame un link de YouTube y lo proceso.\n"
"Los clips aparecen en Nextcloud automáticamente."
)
async def status(update: Update, context: ContextTypes.DEFAULT_TYPE):
if update.effective_chat.id != CHAT_ID:
return
qsize = queue.qsize()
if current_job:
name = current_job[:80]
await update.message.reply_text(f"▶️ Procesando: {name}...\n⏳ En cola: {qsize}")
elif qsize > 0:
await update.message.reply_text(f"⏳ En cola: {qsize}")
else:
await update.message.reply_text("💤 Sin actividad.")
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
if update.effective_chat.id != CHAT_ID:
return
text = update.message.text.strip()
if not re.search(r"(youtube\.com|youtu\.be)", text):
await update.message.reply_text("❌ Mandame un link de YouTube.")
return
qsize = queue.qsize()
await queue.put(text)
if qsize == 0 and not current_job:
await update.message.reply_text("🚀 Iniciando procesamiento...")
else:
await update.message.reply_text(f"⏳ Encolado ({qsize + 1}º en cola).")
def get_video_title(url: str) -> str:
try:
r = subprocess.run(
["yt-dlp", "--print", "title", url],
capture_output=True, text=True, timeout=30,
)
return r.stdout.strip()
except Exception:
return "video"
async def process_queue(bot):
global current_job
while True:
url = await queue.get()
current_job = url
try:
await process_video(bot, url)
except Exception as e:
try:
await bot.send_message(CHAT_ID, f"❌ Error procesando: {e}")
except Exception:
pass
finally:
current_job = None
queue.task_done()
async def process_video(bot, url: str):
title = get_video_title(url)
safe = re.sub(r'[<>:"/\\|?*#\s]+', "_", title)[:80]
folder = f"{safe}_{datetime.now():%Y-%m-%d}"
target = f"{NEXTCLOUD_BASE}/{folder}"
os.makedirs(target, exist_ok=True)
msg = await bot.send_message(CHAT_ID, "🚀 Descargando...")
env = os.environ.copy()
env["NEXTCLOUD_TARGET"] = target
temp_dir = tempfile.mkdtemp(prefix="viral_")
total_clips = 0
cur_clip = 0
last_text = ""
try:
proc = await asyncio.create_subprocess_exec(
sys.executable,
"-u",
str(SCRIPT_DIR / "main.py"),
"-u",
url,
"-o",
temp_dir,
env=env,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
)
async for raw in proc.stdout:
line = raw.decode("utf-8", errors="replace").strip()
if not line:
continue
text = None
if "Transcribing" in line:
text = "🎙️ Transcribiendo..."
elif "Calling local LLM" in line:
text = "🤖 Analizando clips virales..."
elif m := re.search(r"Found\s+(\d+)\s+viral", line, re.IGNORECASE):
total_clips = int(m.group(1))
text = f"🔥 Encontrados {total_clips} clips"
elif m := re.search(r"\bClip\s+(\d+)\]\s+Starting", line):
cur_clip = int(m.group(1))
text = f"🎬 Clip {cur_clip}/?: reencuadrando + subs..."
elif "Normalizing" in line and cur_clip > 0:
text = f"📱 Clip {cur_clip}/{max(total_clips, cur_clip)}: normalizando..."
elif "Copying to Nextcloud" in line and cur_clip > 0:
text = f"📤 Clip {cur_clip}/{max(total_clips, cur_clip)}: subiendo a Nextcloud..."
if text and text != last_text:
last_text = text
try:
await msg.edit_text(text)
except Exception:
pass
await proc.wait()
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
try:
await msg.delete()
except Exception:
pass
async def post_init(app: Application):
asyncio.create_task(process_queue(app.bot))
def main():
app = (
Application.builder()
.token(BOT_TOKEN)
.post_init(post_init)
.build()
)
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("status", status))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
print("🤖 Viral Telegram Bot iniciado.")
app.run_polling(allowed_updates=Update.ALL_TYPES)
if __name__ == "__main__":
main()
+17
View File
@@ -0,0 +1,17 @@
[Unit]
Description=Viral Telegram Bot
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=ren
Group=ren
WorkingDirectory=/home/ren/viral/openshorts
ExecStart=/usr/bin/python3 /home/ren/viral/openshorts/telegram_bot.py
Restart=always
RestartSec=10
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target