From b5a27fa9385ac9b9422838a6142b15904d023dde Mon Sep 17 00:00:00 2001 From: LeoMortari Date: Mon, 27 Oct 2025 14:08:10 -0300 Subject: [PATCH] Ajustes do Gemini --- .gitignore | 4 +- docker-compose.yml | 3 +- requirements.txt | 3 +- video_render/__pycache__/llm.cpython-39.pyc | Bin 5533 -> 6370 bytes .../__pycache__/rendering.cpython-39.pyc | Bin 10198 -> 10656 bytes video_render/llm.py | 109 +++++++++++------- video_render/media.py | 8 +- video_render/messaging.py | 1 - video_render/pipeline.py | 37 +++--- video_render/rendering.py | 19 ++- 10 files changed, 115 insertions(+), 69 deletions(-) diff --git a/.gitignore b/.gitignore index 64e5617..7a2b6cf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ *.pyc *.pyo *.pyd -__pycache__/ +/__pycache__/ *.egg-info/ .eggs/ dist/ @@ -10,7 +10,7 @@ build/ doc/ videos/ outputs/ - +.DS_STORE # Ignore virtual envs venv/ env/ diff --git a/docker-compose.yml b/docker-compose.yml index 9fe75ac..338e355 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,6 @@ services: video-render: restart: unless-stopped build: . - container_name: video-render environment: # - RABBITMQ_PASS=${RABBITMQ_PASS} - RABBITMQ_PASS=L@l321321321 @@ -10,7 +9,7 @@ services: - RABBITMQ_PORT=32790 # - GEMINI_API_KEY=${GEMINI_API_KEY} - GEMINI_API_KEY=AIzaSyB5TPjSPPZG1Qb6EtblhKFAjvCOdY15rcw - - GEMINI_MODEL=${GEMINI_MODEL:-gemini-2.5-pro} + - GEMINI_MODEL=${GEMINI_MODEL:-gemini-2.5-flash} # - OPENROUTER_API_KEY=${OPENROUTER_API_KEY} - OPENROUTER_API_KEY=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 - OPENROUTER_MODEL=${OPENROUTER_MODEL:-openai/gpt-oss-20b:free} diff --git a/requirements.txt b/requirements.txt index 1593182..f38966b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ pillow==9.5.0 numpy>=1.26.0 requests pika -faster-whisper==1.2.0 \ No newline at end of file +faster-whisper==1.2.0 +google-genai diff --git a/video_render/__pycache__/llm.cpython-39.pyc b/video_render/__pycache__/llm.cpython-39.pyc index 72379c37fcd88c5dde3dbcb2a437e46a004cf331..21992660a7376088f93f3a87e25ad00805a9d07e 100644 GIT binary patch literal 6370 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 BV;KMd delta 2855 zcmZWr&2JmW72jDdcbCiMiu$x9mP$cI+@eJRCo#6@p>+!BQXrQWMQ;UqD9}G3$98Y+p+GLZ1jxZPq`o(#Y$YWz zzxjA?X5O25^LzWR$xlWdzfiCg_+423a^rU+_noK6hxbQctPq7NR1I@(j%KMITJ^k} zuiLH-<5_Big}UQ9L}8iT9#-5VZZ9?6KALm;=M`$v{7)2`zpJ_f(6ea)dIjhW-v8`O zpPFj3!J0_5g&-E66WBNzCgV#>8$vYWsfmdyYcTGM;5G~WMz!Tv*?2XIsv+~6K|H<| z)hBLGt}@|I)FaBmc;ZL0-k$2tW3NRG!5Yadc|F;5<_CJ`Sv_b3SA*(C2t_c?&E$mo z9pWZ8)VFo2+)&EdrEY4#R^BbPnOw?@6C+`n%Qp*PXE~Eo@c6dL) zrwTs~)x*K@iQ55Xk;hqsGCmQ8_3`FrYI+`QEj(`kl;S=Bg_y)tZ57cZj_M=@?Ig)1 z=d@=^4v5!ORORi(KHYLx_J zI|CZonqD)g(jt79T8I6Bv@hj!hq4j4vB&aUXMR_nYwvUv3e$?PYWeQ4 zO`UsWTcbVQegO=~3a8f7*4kNEvPgUHY1?{R-!1MbM1eZ9r)(Q-W4D*~?UCkxs>-(6 zHseR4kM`dsACR_52SyZ-4hG7e3iVJG>g`-R_vXhWeiqb@Nwr*8P2VHiR@(yE49JG1 zOxf*kTQHt)=V5^nXxU<*ZHvLXD(J0?A$nZu*>L|S5+hrgI0Z*tR#SESylcd)T4xO* zZ?^*{K$=?9Ap|YNU~}dh$uF|3FE!U>xQRH`S~0}dv&ph~dMC{bR&Qb$w>Wc)hanv` zAUM~9s;h~pxh(N}Ws+uP1g9B@xXqO~|`q=pFUENY3=TJsw;puS-7 z*2zv_ml}Rl;!H%m5w%Jk`3t^?VvbKMHM;ik=b^WCYK6tkC>DN+My2ljnAJ;CsYRpY z&-s%{S--M#%+0O0!q5}!j^JluDj!BTk4t80tKN(|kD!R7qX2FeT=JTo>Q*{DKX?7w z;x%t>advs>+G544%w1nzc%?Fz>gy4&`+}dtnZ3B4$=UlY7UL13ZE2DF&jm5_K+ywJ zMJrA-RVFyTpS0kiMgE%b@MMyI=%+Rcd<54z3E&z{e>04Hnrf>N-Apqr9`cFqCg6D^ zW~m0p=ksr&wuSC_aMU7pP453tx+~8fKDekNL+JSnY*^X)*m>Gxk5hyAk6nyax(Ex{uF58F}v51|tjpvOt6AR9@+glL zSD(VcIe^s0;|$j7Ol(9{>K&OYSHURfm<^EKVk95%tEYHbCEQBhakla}#PbMt@=u3P z;0?-~k?~MgJ9i-o@@r<}#lB_UmIbk6; z9bu+?mzlC%X0jzS?RJ^z_o5(~>6Jrjvb)q|(;;9MAg1^;Q0cIevg@>S9Y)%<=`eDT z=xOIg@va(w`KK?6_X)X}{IqXzp#;9kn@o+kRj>Pevz&Qyz5FJK%Ys%RH`~t#r09W`o0n1wFsmVub)%`o3h;Fe)ocyZ)?76Mp!>k)d zYks%^&u?o`X0owPCL3!8O%?_XR@Rcg^glhdWnZmSmd94uI^&FQ88^o&fe0BN$DJ}A zelRwe4@ofQlQ0Mbv=IesEKY_8Cdq~5^?_gRU|@h_SFq|jmKAcDuZ0YH_6I0mKvBwl z9m;tN+DBaKMVEjZn=R4xOj^OejcPJFU%=Lj0BQfy^4#Le)Y2Q3xs}J%#HUb>r`(BS zd_Mdogc*eI0+j7eSYJ$jJ2!dydB=7CXue)<|VuOoaP;j8vZKKNrYb?nuh6_nmUs31rJq1bu@ zK_1YPxw0h#@lBB4f^x_k+2YH<8#r1XaxKqWZ(%z0JdPhZ@|Ij9jH=b3QB8rk1$>~b z@(g?r{`c6x4&IupLt=t_$qk$!sRvn}BPqB!$O#KD8nq=KlbYU9CI- diff --git a/video_render/__pycache__/rendering.cpython-39.pyc b/video_render/__pycache__/rendering.cpython-39.pyc index 18da5d3d83d76b06c40d67abc91f0aa05449a052..6577a62a9fbb680d3d7a58da07edc64f949e30a3 100644 GIT binary patch delta 3108 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 delta 2505 zcmZuzZ)_vQ72g@JH|w>%-uTaY?e)LdNiNRaC2E0k9H%29*E`Vyt>Dld3s;k5H$De% z?3>+CK;uiLPy~phxlRl8A9X7A5B*l9{elo*5MMw-AS967N)=MSpbEjK7EXxc&Dy$) z0$$DUy*Ka8doyol-nf77`#(^Z;&DZQU-6#*o%fGiRtxCU%Ll8QxGh?uE!d%U*p(~^ z3BZf(F;})^H)2Ix#Zug;6%G1vJ7&c}A8xB|!b-TBrMctQxSO<+ZpumpHc~t7W~_{> zTe_RIvaVqn&_8C&?VOvp@&O-dn{L4>xJ9dY8VL^Wmz?qrvMP4Qjs{=sw;?-jt51Zj z2}e35*a=&ELa?R zU3M9zGlR$-?o!j?@<2ZAJn9FUKcl1Jj}YXAeTcSA9hf>MSs=s!cv&C?AXm(}dG-vR ziswM^Fo1waiM@*NK?+NR8V9&hU^#>$7s~*g@k9Ux%ZQZOFGJ58Ne~C(-JNykWjv5Q z$M;+7O^@Vu7sXRZC*z?0h=;g>Duhq0o+tI)e)ur@c0Cxmyxe(oU#x-LXtdT_exor7 zCSDalKoU|AyDDW7V%f19kL4 zgTVh#o<^v^Zi~$htph{lSi9x9c{6V%E`z{)1Z^Z6GCQHR%I}$iO3;sxu&^v&8 z0Gc-kAfO}`k&I%_%5N?$V{20jT2%6_k9ib#bJ5`@r@hLw|JdZ<=7Y6!_hqs z7#Y7g%6Ej+I83s?j~|@Bt`E3idDEcgCSD9cMp`CW)h4tOMr4|)$=9R&+>iqtX4t=y z--+jet%nFd6kt(KWwe03?z+fMq>i8z>!mJ2Hoi!m<=MC|^HWq}|IAckmf~$JufT2S zI`+@?LvTP2>+UUkhJ)jQG~Dq&0q>mv5M&X@AO~q^!;dE)LmzujKM6ka*|{>mB`Xnb zJ^Jyb@l`Ftws~iFo7tNXz9g}}vAC&T!o7S`=$X(8G)m)!K;u+cORwn$RHeL-6?#RQ zKOvlZuvem`wQN6PCungM@1S0pntg*p=|BQ400-KDFRkUkex_HUl|9xIf%U{S>k8*K zx6y@np!KP_jlJ^_I8MXcxT{f-Ds}{>puhyHe5kL|s-FN0VGXTmTWG}PM>J_iha+g* zjv2x#w&Rziew0Rb(Dh01kiJP%G)*%!NsZ=an3{h;RH0EAph2h_mF;mV*~#CCcItN_ zQ|L|7+FAhuPhJaI9EB{6Xn8~{c6!83Kq=LmB8-yl?8uwJy=@^oXXkD6QUr2V>sMEI z1Yop&HK556HAXZwqUjOMjHn6eReMu(YAA1qE*zqTuk|eqCk++3z!uCiXqLTbmg04Z zG~g(Z8v!i#mRU&D5#dK`Agws-zC&2K@Rv}H)Y+Sb^C-%0C@vZmKHx46yjqbPIPmku z{#Yy*pW@Qf9G(HNq%LuwsFEHC_37OYi#lQtl+PaQZ1~*`ztLUt&s!ot_WQ3tg#jNx z^WJCwEf)}Ga^l-NJIu=Yn7_525K=P{U2Wy;~1)f^raML zf35W3!p%%zG|Mk3@%U5_d6x70?w5S5z2q+2OUEB(r>hT1X>MY&SE@hSUxR)In)}$H z+DlMj-l~1_wTkvl-h+zv2>Z|E_s}+*pUNKPRh&G=;T#8tLy%S;x2}-|-VyB1^PGEu z!;2h##eOpN{ryw=1!&gUzo+hoDa}p4iGIyKpFXEcfH)%`f965 zg7SNTt5O_z#;i!w@$c+3n+_qt>K^AB{&ohBKjCFBnAA6|WFQ}NJN8D~IgY^0s{q8{ ZCF9@lO)HEUP*!97ok@#uw!-SguK=n;4gLTC diff --git a/video_render/llm.py b/video_render/llm.py index de6c4ae..84d2d4f 100644 --- a/video_render/llm.py +++ b/video_render/llm.py @@ -3,8 +3,10 @@ from __future__ import annotations import json import logging from pathlib import Path -from typing import Dict, List +from typing import Any, Dict, List, Optional +from google import genai +from google.genai import types as genai_types import requests from video_render.config import BASE_DIR, Settings @@ -12,7 +14,6 @@ from video_render.transcription import TranscriptionResult logger = logging.getLogger(__name__) -GEMINI_ENDPOINT_TEMPLATE = "https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent" OPENROUTER_ENDPOINT = "https://openrouter.ai/api/v1/chat/completions" @@ -31,6 +32,7 @@ class GeminiHighlighter: self.prompt_template = prompt_path.read_text(encoding="utf-8") self.settings = settings + self.client = genai.Client() def generate_highlights(self, transcription: TranscriptionResult) -> List[Dict]: payload = { @@ -45,45 +47,13 @@ class GeminiHighlighter: ], } - body = { - "contents": [ - { - "role": "user", - "parts": [ - {"text": self.prompt_template}, - {"text": json.dumps(payload, ensure_ascii=False)}, - ], - } - ] - } + try: + response = self._call_gemini(payload) + except Exception as exc: + logger.error("Gemini API request falhou: %s", exc) + raise RuntimeError("Gemini API request falhou") from exc - if self.settings.gemini.temperature is not None: - body["generationConfig"] = { - "temperature": self.settings.gemini.temperature, - } - if self.settings.gemini.top_p is not None: - body["generationConfig"]["topP"] = self.settings.gemini.top_p - if self.settings.gemini.top_k is not None: - body["generationConfig"]["topK"] = self.settings.gemini.top_k - - url = GEMINI_ENDPOINT_TEMPLATE.format(model=self.settings.gemini.model) - params = {"key": self.settings.gemini.api_key} - - response = requests.post(url, params=params, json=body, timeout=120) - response.raise_for_status() - data = response.json() - - candidates = data.get("candidates") or [] - if not candidates: - raise RuntimeError("Gemini nao retornou candidatos") - - text_parts = candidates[0].get("content", {}).get("parts", []) - if not text_parts: - raise RuntimeError("Resposta do Gemini sem conteudo") - - raw_text = text_parts[0].get("text") - if not raw_text: - raise RuntimeError("Resposta do Gemini sem texto") + raw_text = self._extract_response_text(response) parsed = self._extract_json(raw_text) highlights = parsed.get("highlights") @@ -91,6 +61,61 @@ class GeminiHighlighter: raise ValueError("Resposta do Gemini invalida: campo 'highlights' ausente") return highlights + def _call_gemini(self, payload: Dict[str, Any]) -> Any: + contents = [ + { + "role": "user", + "parts": [ + {"text": self.prompt_template}, + {"text": json.dumps(payload, ensure_ascii=False)}, + ], + } + ] + + request_kwargs: Dict[str, Any] = { + "model": self.settings.gemini.model, + "contents": contents, + } + + config = self._build_generation_config() + if config is not None: + request_kwargs["config"] = config + + return self.client.models.generate_content(**request_kwargs) + + def _build_generation_config(self) -> Optional[genai_types.GenerateContentConfig]: + config_kwargs: Dict[str, Any] = {} + if self.settings.gemini.temperature is not None: + config_kwargs["temperature"] = self.settings.gemini.temperature + if self.settings.gemini.top_p is not None: + config_kwargs["top_p"] = self.settings.gemini.top_p + if self.settings.gemini.top_k is not None: + config_kwargs["top_k"] = self.settings.gemini.top_k + + if not config_kwargs: + return None + + return genai_types.GenerateContentConfig(**config_kwargs) + + @staticmethod + def _extract_response_text(response: Any) -> str: + text = getattr(response, "text", None) + if text: + return str(text).strip() + + candidates = getattr(response, "candidates", None) or [] + for candidate in candidates: + content = getattr(candidate, "content", None) + if not content: + continue + parts = getattr(content, "parts", None) or [] + for part in parts: + part_text = getattr(part, "text", None) + if part_text: + return str(part_text).strip() + + raise RuntimeError("Resposta do Gemini sem texto") + @staticmethod def _extract_json(response_text: str) -> Dict: try: @@ -160,10 +185,6 @@ class OpenRouterCopywriter: response.raise_for_status() data = response.json() choices = data.get("choices") or [] - print("Data:") - print(data) - print("Choices:") - print(choices) if not choices: raise RuntimeError("OpenRouter nao retornou escolhas") diff --git a/video_render/media.py b/video_render/media.py index 7fb878e..a79dd4f 100644 --- a/video_render/media.py +++ b/video_render/media.py @@ -38,7 +38,7 @@ class MediaPreparer: existing_children = list(workspace_dir.iterdir()) if existing_children: logger.info("Limpando workspace existente para %s", sanitized_name) - remove_paths(existing_children) + # remove_paths(existing_children) destination_name = f"{sanitized_name}{source_path.suffix.lower()}" working_video_path = workspace_dir / destination_name @@ -46,9 +46,9 @@ class MediaPreparer: logger.info("Cópia do vídeo criada em %s", working_video_path) output_dir = ensure_workspace(self.settings.outputs_dir, sanitized_name) - existing_outputs = list(output_dir.iterdir()) - if existing_outputs: - remove_paths(existing_outputs) + # existing_outputs = list(output_dir.iterdir()) + # if existing_outputs: + # remove_paths(existing_outputs) audio_path = workspace_dir / "audio.wav" extract_audio_to_wav(working_video_path, audio_path) diff --git a/video_render/messaging.py b/video_render/messaging.py index c37058d..b61599c 100644 --- a/video_render/messaging.py +++ b/video_render/messaging.py @@ -15,7 +15,6 @@ 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, diff --git a/video_render/pipeline.py b/video_render/pipeline.py index 6bd6689..c8e309e 100644 --- a/video_render/pipeline.py +++ b/video_render/pipeline.py @@ -101,7 +101,15 @@ class VideoPipeline: if not context.transcription: raise RuntimeError("Transcricao nao disponivel") - highlights_raw = self.highlighter.generate_highlights(context.transcription) + try: + highlights_raw = self.highlighter.generate_highlights(context.transcription) + except Exception: + logger.exception( + "Falha ao gerar destaques com Gemini; aplicando fallback padrao." + ) + context.highlight_windows = [self._build_fallback_highlight(context)] + return + windows: List[HighlightWindow] = [] for item in highlights_raw: @@ -120,18 +128,7 @@ class VideoPipeline: windows.append(HighlightWindow(start=start, end=end, summary=summary)) if not windows: - last_end = ( - context.transcription.segments[-1].end - if context.transcription.segments - else 0 - ) - windows.append( - HighlightWindow( - start=0.0, - end=max(last_end, 10.0), - summary="Sem destaque identificado; fallback automatico.", - ) - ) + windows.append(self._build_fallback_highlight(context)) context.highlight_windows = windows @@ -148,6 +145,20 @@ class VideoPipeline: for window, title in zip(context.highlight_windows, titles): window.title = title.strip() + def _build_fallback_highlight(self, context: PipelineContext) -> HighlightWindow: + if not context.transcription: + raise RuntimeError("Transcricao nao disponivel para criar fallback") + + last_end = ( + context.transcription.segments[-1].end + if context.transcription.segments + else 0.0 + ) + return HighlightWindow( + start=0.0, + end=max(last_end, 10.0), + summary="Sem destaque identificado; fallback automatico.", + ) def _render_clips(self, context: PipelineContext) -> None: if not context.workspace or not context.highlight_windows or not context.transcription: diff --git a/video_render/rendering.py b/video_render/rendering.py index f09ab87..723f17d 100644 --- a/video_render/rendering.py +++ b/video_render/rendering.py @@ -267,6 +267,7 @@ class VideoRenderer: color=self.settings.rendering.base_color, method="caption", size=(frame_w - 160, top_h - 40), + align="center", ) .with_duration(duration) ) @@ -279,8 +280,18 @@ class VideoRenderer: caption_clips = [] caption_resources: List[ImageClip] = [] - margin = 20 - caption_y = max(0, video_y - self.captions.canvas_height - margin) + caption_area_top = frame_h - bottom_h + caption_area_height = bottom_h + caption_margin = 20 + raw_caption_y = caption_area_top + (caption_area_height - self.captions.canvas_height) // 2 + min_caption_y = caption_area_top + caption_margin + max_caption_y = ( + caption_area_top + caption_area_height - self.captions.canvas_height - caption_margin + ) + if max_caption_y < min_caption_y: + caption_y = min_caption_y + else: + caption_y = min(max(raw_caption_y, min_caption_y), max_caption_y) for clip_set in caption_sets: base_positioned = clip_set.base.with_position(("center", caption_y)) @@ -300,6 +311,7 @@ class VideoRenderer: font_size=self.settings.rendering.subtitle_font_size, color=self.settings.rendering.base_color, method="caption", + align="center", size=(frame_w - 160, max(40, self.captions.canvas_height)), ) .with_duration(duration) @@ -310,6 +322,9 @@ class VideoRenderer: [background, top_panel, bottom_panel, video_clip, title_clip, *caption_clips], size=(frame_w, frame_h), ) + video_audio = video_clip.audio or resized_clip.audio or subclip.audio + if video_audio is not None: + composite = composite.set_audio(video_audio) output_path = output_dir / f"clip_{index:02d}.mp4" composite.write_videofile(