feat: telegram bot + normalize for mobile + nextcloud subcarpetas
This commit is contained in:
+24
-1
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user