From ba768cf093a73230893a60547a212f841bedbf9a Mon Sep 17 00:00:00 2001 From: LeoMortari Date: Sat, 25 Oct 2025 00:54:30 -0300 Subject: [PATCH] Ajusta demais partes do projeto --- Montserrat.ttf | Bin 0 -> 29016 bytes docker-compose.yml | 20 +++++--------------- dockerfile | 8 -------- video_render/config.py | 4 ++-- video_render/llm.py | 7 ++++--- video_render/messaging.py | 2 ++ video_render/pipeline.py | 1 + video_render/rendering.py | 28 ++++++++++++++++------------ 8 files changed, 30 insertions(+), 40 deletions(-) create mode 100644 Montserrat.ttf diff --git a/Montserrat.ttf b/Montserrat.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5b4b5afe6ee4b560b65b2f2040ad38f6c094b347 GIT binary patch literal 29016 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 literal 0 HcmV?d00001 diff --git a/docker-compose.yml b/docker-compose.yml index 3f4fbc5..9fe75ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,31 +1,21 @@ -# GEMINI_API_KEY="AIzaSyB5TPjSPPZG1Qb6EtblhKFAjvCOdY15rcw" -# YOUTUBE_API="https://totally-real-dingo.ngrok-free.app" -# OPENROUTER_API_KEY="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" -# OPENROUTER_MODEL="openai/gpt-oss-20b:free" - services: video-render: restart: unless-stopped build: . container_name: video-render environment: - # RabbitMQ credentials # - RABBITMQ_PASS=${RABBITMQ_PASS} - - RABBITMQ_PASS="L@l321321321" + - RABBITMQ_PASS=L@l321321321 + - RABBITMQ_HOST=154.12.229.181 + - RABBITMQ_PORT=32790 # - GEMINI_API_KEY=${GEMINI_API_KEY} - - GEMINI_API_KEY="AIzaSyB5TPjSPPZG1Qb6EtblhKFAjvCOdY15rcw" + - GEMINI_API_KEY=AIzaSyB5TPjSPPZG1Qb6EtblhKFAjvCOdY15rcw - GEMINI_MODEL=${GEMINI_MODEL:-gemini-2.5-pro} # - OPENROUTER_API_KEY=${OPENROUTER_API_KEY} - - OPENROUTER_API_KEY="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" + - OPENROUTER_API_KEY=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 - OPENROUTER_MODEL=${OPENROUTER_MODEL:-openai/gpt-oss-20b:free} - FASTER_WHISPER_MODEL_SIZE=${FASTER_WHISPER_MODEL_SIZE:-small} - # ports: - # - "5000:5000" volumes: - # Mount host directories into the container so that videos can be - # provided and outputs collected. These paths can be customised when - # deploying the stack. The defaults assume /root/videos and - # /root/outputs on the host. # - "/root/videos:/app/videos" # - "/root/outputs:/app/outputs" - "./videos:/app/videos" diff --git a/dockerfile b/dockerfile index ec261de..d146341 100644 --- a/dockerfile +++ b/dockerfile @@ -2,12 +2,10 @@ FROM python:3.11-slim WORKDIR /app -# Set environment variables ENV DEBIAN_FRONTEND=noninteractive \ PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 -# Install system dependencies RUN apt-get update && \ apt-get install -y --no-install-recommends \ ffmpeg \ @@ -27,22 +25,16 @@ RUN apt-get update && \ wget \ && rm -rf /var/lib/apt/lists/* -# Copy requirements first to leverage Docker cache COPY requirements.txt . -# Install Python dependencies RUN pip install --no-cache-dir --upgrade pip && \ pip install --no-cache-dir setuptools wheel && \ pip install --no-cache-dir -r requirements.txt -# Copy the rest of the application COPY . . -# Create necessary directories RUN mkdir -p /app/videos /app/outputs -# Set volumes VOLUME ["/app/videos", "/app/outputs"] -# Set the command to run your application CMD ["python", "-u", "main.py"] \ No newline at end of file diff --git a/video_render/config.py b/video_render/config.py index ee42f54..37d560c 100644 --- a/video_render/config.py +++ b/video_render/config.py @@ -16,7 +16,7 @@ class RabbitMQSettings: host: str = os.environ.get("RABBITMQ_HOST", "rabbitmq") port: int = int(os.environ.get("RABBITMQ_PORT", 5672)) user: str = os.environ.get("RABBITMQ_USER", "admin") - password: str = os.environ.get("RABBITMQ_PASS", "") + password: str = os.environ.get("RABBITMQ_PASS") consume_queue: str = os.environ.get("RABBITMQ_QUEUE", "to-render") publish_queue: str = os.environ.get("RABBITMQ_UPLOAD_QUEUE", "to-upload") prefetch_count: int = int(os.environ.get("RABBITMQ_PREFETCH", 1)) @@ -27,7 +27,7 @@ class RabbitMQSettings: @dataclass(frozen=True) class GeminiSettings: api_key: str = os.environ.get("GEMINI_API_KEY", "") - model: str = os.environ.get("GEMINI_MODEL", "gemini-1.5-pro-latest") + model: str = os.environ.get("GEMINI_MODEL", "gemini-2.5-pro") safety_settings: str | None = os.environ.get("GEMINI_SAFETY_SETTINGS") temperature: float = float(os.environ.get("GEMINI_TEMPERATURE", 0.2)) top_k: int | None = ( diff --git a/video_render/llm.py b/video_render/llm.py index c0742bc..2437fb5 100644 --- a/video_render/llm.py +++ b/video_render/llm.py @@ -150,8 +150,6 @@ class OpenRouterCopywriter: headers = { "Authorization": f"Bearer {self.settings.openrouter.api_key}", "Content-Type": "application/json", - "HTTP-Referer": "https://localhost", - "X-Title": "video-render-pipeline", } response = requests.post( @@ -159,19 +157,22 @@ class OpenRouterCopywriter: ) response.raise_for_status() data = response.json() - choices = data.get("choices") or [] + if not choices: raise RuntimeError("OpenRouter nao retornou escolhas") message = choices[0].get("message", {}).get("content") + if not message: raise RuntimeError("Resposta do OpenRouter sem conteudo") parsed = self._extract_json(message) titles = parsed.get("titles") + if not isinstance(titles, list): raise ValueError("Resposta do OpenRouter invalida: campo 'titles'") + return [str(title) for title in titles] @staticmethod diff --git a/video_render/messaging.py b/video_render/messaging.py index 08ead1d..c37058d 100644 --- a/video_render/messaging.py +++ b/video_render/messaging.py @@ -15,6 +15,7 @@ MessageHandler = Callable[[Dict[str, Any]], Dict[str, Any]] class RabbitMQWorker: def __init__(self, settings: Settings) -> None: + print(settings) self.settings = settings self._params = pika.ConnectionParameters( host=settings.rabbitmq.host, @@ -27,6 +28,7 @@ class RabbitMQWorker: ) def consume_forever(self, handler: MessageHandler) -> None: + while True: try: with pika.BlockingConnection(self._params) as connection: diff --git a/video_render/pipeline.py b/video_render/pipeline.py index 0b33843..a7fc042 100644 --- a/video_render/pipeline.py +++ b/video_render/pipeline.py @@ -74,6 +74,7 @@ class VideoPipeline: def _parse_job(self, message: Dict[str, Any]) -> JobMessage: filename = message.get("filename") + if not filename: raise ValueError("Mensagem inválida: 'filename' é obrigatório") diff --git a/video_render/rendering.py b/video_render/rendering.py index efd45e0..070427c 100644 --- a/video_render/rendering.py +++ b/video_render/rendering.py @@ -1,19 +1,14 @@ from __future__ import annotations import logging -import math import re from dataclasses import dataclass from typing import Iterable, List, Sequence, Tuple import numpy as np -from moviepy.editor import ( - ColorClip, - CompositeVideoClip, - ImageClip, - TextClip, - VideoFileClip, -) +from moviepy.video.VideoClip import ColorClip, ImageClip, TextClip +from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip +from moviepy.video.io.VideoFileClip import VideoFileClip from PIL import Image, ImageColor, ImageDraw, ImageFont from video_render.config import Settings @@ -36,6 +31,7 @@ class CaptionBuilder: def __init__(self, settings: Settings) -> None: self.settings = settings self.font_path = settings.rendering.font_path + if not self.font_path.exists(): raise FileNotFoundError(f"Fonte nao encontrada: {self.font_path}") @@ -50,6 +46,7 @@ class CaptionBuilder: self.max_words = settings.rendering.caption_max_words bbox = self.font.getbbox("Ay") + self.text_height = bbox[3] - bbox[1] self.baseline = (self.canvas_height - self.text_height) // 2 - bbox[1] self.space_width = self.font.getbbox(" ")[2] - self.font.getbbox(" ")[0] @@ -73,6 +70,7 @@ class CaptionBuilder: ) highlight_clips: List[ImageClip] = [] + for word, image in zip(group, highlight_images): h_start = clamp_time(word.start, minimum=clip_start) - clip_start h_end = clamp_time(word.end, minimum=word.start + 0.02) - clip_start @@ -90,13 +88,14 @@ class CaptionBuilder: def _render_group(self, group: Sequence[WordTiming]) -> Tuple[Image.Image, List[Image.Image]]: texts = [self._clean_word(word.word) for word in group] - widths = [] + for text in texts: bbox = self.font.getbbox(text) widths.append(bbox[2] - bbox[0]) total_width = sum(widths) + if len(widths) > 1: total_width += self.space_width * (len(widths) - 1) @@ -105,8 +104,8 @@ class CaptionBuilder: base_image = Image.new("RGBA", (self.canvas_width, self.canvas_height), (0, 0, 0, 0)) base_draw = ImageDraw.Draw(base_image) highlight_images: List[Image.Image] = [] - x = start_x + for text, width in zip(texts, widths): base_draw.text((x, self.baseline), text, font=self.font, fill=self.base_color) @@ -130,6 +129,7 @@ class CaptionBuilder: for word in words: buffer.append(word) + if len(buffer) == self.max_words: grouped.append(buffer) buffer = [] @@ -140,7 +140,6 @@ class CaptionBuilder: else: grouped.append(buffer) - # Rebalance groups to respect minimum size when possible for idx, group in enumerate(grouped[:-1]): if len(group) < self.min_words and len(grouped[idx + 1]) > self.min_words: deficit = self.min_words - len(group) @@ -149,6 +148,7 @@ class CaptionBuilder: grouped[idx + 1] = grouped[idx + 1][deficit:] grouped = [grp for grp in grouped if grp] + return grouped @staticmethod @@ -175,16 +175,20 @@ class VideoRenderer: with VideoFileClip(workspace_path) as base_clip: video_duration = base_clip.duration or 0 + for index, window in enumerate(highlight_windows, start=1): start = clamp_time(window.start) end = clamp_time(window.end) start = min(start, video_duration) end = min(end, video_duration) + if end <= start: logger.info("Janela ignorada por intervalo invalido: %s", window) + continue subclip = base_clip.subclipped(start, end) + try: rendered_path = self._render_single_clip( subclip=subclip, @@ -236,7 +240,6 @@ class VideoRenderer: ) resized_clip = subclip.resized(scale_factor) video_y = top_h + (video_area_h - resized_clip.h) // 2 - video_clip = resized_clip.with_position( ((frame_w - resized_clip.w) // 2, video_y) ) @@ -277,6 +280,7 @@ class VideoRenderer: caption_clips = [] caption_resources: List[ImageClip] = [] caption_y = frame_h - bottom_h + (bottom_h - self.captions.canvas_height) // 2 + for clip_set in caption_sets: base_positioned = clip_set.base.with_position(("center", caption_y)) caption_clips.append(base_positioned)