<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Midia-Generativa | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/midia-generativa/</link><description>Blog do Fabio Akita do Canal do YouTube 'Akitando' falando sobre tecnologia, carreira e coisas geek.</description><generator>Hugo -- gohugo.io</generator><language>pt-BR</language><lastBuildDate>Mon, 19 Oct 2026 23:20:48 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/tags/midia-generativa/index.xml" rel="self" type="application/rss+xml"/><item><title>Seedance 2.0 finalmente foi lançado: Primeiras Impressões</title><link>https://akitaonrails.github.io/2026/04/15/seedance-2-0-finalmente-foi-lancado-primeiras-impressoes/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/04/15/seedance-2-0-finalmente-foi-lancado-primeiras-impressoes/</guid><pubDate>Wed, 15 Apr 2026 10:00:00 -0300</pubDate><description>&lt;p&gt;A ByteDance lançou hoje, 15 de abril, a versão pública do &lt;a href="https://seed.bytedance.com/en/blog/official-launch-of-seedance-2-0"target="_blank" rel="noopener"&gt;Seedance 2.0&lt;/a&gt;. Eles prometeram esse modelo no fim do ano passado, mas o acesso ficou restrito a parceiros comerciais por meses, com vários adiamentos pelo caminho. Hoje finalmente abriu pra qualquer um testar.&lt;/p&gt;
&lt;p&gt;A proposta é forte. É um modelo multimodal de geração de vídeo que aceita simultaneamente texto, até 9 imagens de referência, 3 clipes de vídeo de referência e 3 trilhas de áudio numa mesma chamada. Em cima disso, ele gera saída de até 15 segundos com áudio sincronizado nativo (diálogo, efeitos, música ambiente), suporta extensão de vídeo, edição pontual de personagens e objetos, e tem planejamento de câmera dirigido por prompt. A ByteDance posiciona o modelo pra publicidade comercial, vídeos explicativos, produção de filme, e-commerce e gaming. Ou seja: pra criador de conteúdo que quer escapar do estágio &amp;ldquo;meme aleatório&amp;rdquo; e começar a entregar peça com uso profissional.&lt;/p&gt;</description><content:encoded><![CDATA[<p>A ByteDance lançou hoje, 15 de abril, a versão pública do <a href="https://seed.bytedance.com/en/blog/official-launch-of-seedance-2-0"target="_blank" rel="noopener">Seedance 2.0</a>. Eles prometeram esse modelo no fim do ano passado, mas o acesso ficou restrito a parceiros comerciais por meses, com vários adiamentos pelo caminho. Hoje finalmente abriu pra qualquer um testar.</p>
<p>A proposta é forte. É um modelo multimodal de geração de vídeo que aceita simultaneamente texto, até 9 imagens de referência, 3 clipes de vídeo de referência e 3 trilhas de áudio numa mesma chamada. Em cima disso, ele gera saída de até 15 segundos com áudio sincronizado nativo (diálogo, efeitos, música ambiente), suporta extensão de vídeo, edição pontual de personagens e objetos, e tem planejamento de câmera dirigido por prompt. A ByteDance posiciona o modelo pra publicidade comercial, vídeos explicativos, produção de filme, e-commerce e gaming. Ou seja: pra criador de conteúdo que quer escapar do estágio &ldquo;meme aleatório&rdquo; e começar a entregar peça com uso profissional.</p>
<p>Pra quem quer ver o modelo em ação antes de continuar lendo, recomendo essa visão geral feita pelo Stefan, do canal <a href="https://www.youtube.com/channel/UCRW08KcTVjXEmBzBsVl7XjA"target="_blank" rel="noopener">Stefan 3D AI Lab</a>, que eu já tinha indicado lá no <a href="/2026/01/23/ai-3d-ja-da-pra-modelar-3d-com-prompts/">post sobre modelagem 3D com IA</a>:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/fv9vA6RCNHU"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>A interface principal é o &ldquo;Photo Studio&rdquo; da Dreamina, onde você escolhe o modo de geração e empilha as referências. Por enquanto eu testei só o modo de multi-referência padrão.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/15/seedance/studio.png" alt="A interface do Photo Studio do Seedance 2.0, com painel de upload de referências e configuração de prompt"  loading="lazy" /></p>
<h2>Teste 1: lip-sync com áudio do podcast<span class="hx:absolute hx:-mt-20" id="teste-1-lip-sync-com-áudio-do-podcast"></span>
    <a href="#teste-1-lip-sync-com-%c3%a1udio-do-podcast" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Meu primeiro teste foi simples. Peguei alguns segundos do bumper de abertura do meu podcast <a href="/tags/the-m-akita-chronicles/">The M.Akita Chronicles</a>, que é gerado com IA via <a href="/2026/04/09/como-a-elevenlabs-nao-foi-morta-pelo-qwen3-tts/">pipeline ElevenLabs v3</a>, passei o meu novo avatar em estilo anime como imagem de referência, e pedi pro Seedance fazer o personagem sincronizar boca e gesticular em cima daquele áudio.</p>
<div style="max-width: 100%; margin: 1em 0;">
  <video controls playsinline style="width: 100%; border-radius: 8px;">
    <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/15/seedance/test1.mp4" type="video/mp4">
  </video>
  <em>Avatar anime sincronizando boca com áudio do bumper do M.Akita Chronicles</em>
</div>
<p>O resultado é decente. Saiu tudo de uma imagem fixa só. O lip-sync acompanha a fala razoavelmente bem, a expressão tem alguma vida, o gesto da mão entra no momento certo. Mas é o tipo de coisa que rende clipinho curto pro YouTube ou Instagram. Não é animação que você usaria como abertura de um vídeo profissional.</p>
<h2>A função que de fato importa: vídeo de referência<span class="hx:absolute hx:-mt-20" id="a-função-que-de-fato-importa-vídeo-de-referência"></span>
    <a href="#a-fun%c3%a7%c3%a3o-que-de-fato-importa-v%c3%addeo-de-refer%c3%aancia" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>E aqui eu chego no que eu considero o único uso de verdade desses modelos pra trabalho sério: usar <strong>vídeo como referência</strong>. Texto sozinho nunca vai ser preciso o suficiente. Você pode descrever a cena com o vocabulário que quiser, mudar adjetivo, especificar lente, ângulo, framing, e ainda assim cada geração vai te dar algo um pouco diferente. É loteria. Bom pra meme, péssimo pra produção.</p>
<p>A maioria dos amadores se impressiona com qualquer clipe aleatório que sai bonito de primeira, e enche o feed do X e do Instagram com isso. Só que aquilo não é controle. É sorte. Pra cinema, pra publicidade, pra abertura de programa, pra qualquer coisa onde a próxima cena precisa casar com a anterior, você tem que conseguir dizer pro modelo: &ldquo;faça exatamente esse movimento, com exatamente essa câmera&rdquo;. E é aí que o vídeo de referência muda o jogo, porque ele substitui horas de modelagem 3D, rigging, animação e renderização que custariam fortunas em estúdio.</p>
<p>Pro meu teste, eu reciclei o modelo que já tinha gerado lá no <a href="/2026/01/23/ai-3d-ja-da-pra-modelar-3d-com-prompts/">post sobre AI 3D com Hunyuan e Nano Banana</a>, onde testei o quanto dá pra modelar 3D do nada com prompt e ainda imprimir em 3D. Mas pra uma referência mais cinematográfica eu queria algo com animação pronta. Baixei do Sketchfab o modelo <a href="https://sketchfab.com/3d-models/darth-talon-4338105e09704f359c6afcab7e5fce10"target="_blank" rel="noopener">Darth Talon</a>, que tem uma sequência curta de movimento bem feita, só porque achei legal.</p>
<p>Importei o FBX no Blender, montei câmera e luz básica, e fiz uma renderização rápida no Cycles. Nada bonito, é só um esboço pra servir de referência de movimento e enquadramento.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/15/seedance/blender.png" alt="O modelo Darth Talon importado no Blender com câmera e iluminação configuradas pra render rápido no Cycles"  loading="lazy" /></p>
<div style="max-width: 100%; margin: 1em 0;">
  <video controls playsinline style="width: 100%; border-radius: 8px;">
    <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/15/seedance/blender-render.mp4" type="video/mp4">
  </video>
  <em>O render rápido no Cycles que vai virar referência de movimento</em>
</div>
<p>Subi pro Seedance esse render como vídeo de referência junto com três imagens em t-pose do personagem (frente, costas, lado esquerdo) pra dar consistência visual:</p>
<div style="display: flex; gap: 8px; flex-wrap: wrap; margin: 1em 0; justify-content: center;">
  <img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/15/seedance/front.jpg" alt="T-pose frontal" style="flex: 1 1 30%; min-width: 140px; max-width: 32%; border-radius: 8px;" />
  <img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/15/seedance/back.jpg" alt="T-pose de costas" style="flex: 1 1 30%; min-width: 140px; max-width: 32%; border-radius: 8px;" />
  <img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/15/seedance/left.jpg" alt="T-pose lateral esquerda" style="flex: 1 1 30%; min-width: 140px; max-width: 32%; border-radius: 8px;" />
</div>
<p>Primeira tentativa, com prompt genérico:</p>
<div style="max-width: 100%; margin: 1em 0;">
  <video controls playsinline style="width: 100%; border-radius: 8px;">
    <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/15/seedance/test2.mp4" type="video/mp4">
  </video>
  <em>Primeira geração: o personagem ficou consistente, mas o movimento e a câmera não bateram com a referência</em>
</div>
<p>Não ficou ruim. O personagem mantém identidade, a iluminação melhorou bastante em cima do meu render bruto, e o vídeo é coerente. Só que o movimento e a posição de câmera divergiram bastante da referência. O modelo entendeu &ldquo;ali tem um personagem parecido com esse fazendo algo parecido com aquilo&rdquo;, e improvisou o resto.</p>
<p>Lembrei do tutorial do Stefan: o truque é colocar literalmente no prompt algo como <code>Follow exact motion and camera from reference video</code>. Tentei de novo:</p>
<div style="max-width: 100%; margin: 1em 0;">
  <video controls playsinline style="width: 100%; border-radius: 8px;">
    <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/15/seedance/test3.mp4" type="video/mp4">
  </video>
  <em>Segunda geração: instrução explícita pra seguir movimento e câmera da referência</em>
</div>
<p>Bem melhor. Está claramente seguindo a coreografia da referência, o ângulo de câmera bate em vários momentos, e a aparência do personagem ficou mais polida que tudo que eu poderia renderizar à mão num esboço de Cycles. Mas ainda assim não é cópia exata do meu render. Em alguns frames o modelo ainda toma liberdade com timing, posição e enquadramento. Existem outras técnicas de prompting e parâmetros pra ajustar, mas eu parei aqui pra registrar essas primeiras impressões enquanto o lançamento está fresco.</p>
<h2>ComfyUI, Runway e o resto do ecossistema<span class="hx:absolute hx:-mt-20" id="comfyui-runway-e-o-resto-do-ecossistema"></span>
    <a href="#comfyui-runway-e-o-resto-do-ecossistema" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O Seedance 2.0 também já saiu <a href="https://blog.comfy.org/p/seedance-20-is-now-available-in-comfyui"target="_blank" rel="noopener">como nó do ComfyUI na nuvem</a>, o que ajuda muito quem quer encaixar a geração dentro de pipeline maior sem ficar pulando entre interface web. Pra quem já tem pipeline de geração de imagem montado em ComfyUI, isso é bem prático.</p>
<p>E pra quem quer um caminho com volume sem pensar em crédito por geração, a Runway está oferecendo o Seedance 2.0 dentro do <a href="https://www.mindstudio.ai/blog/seedance-2-0-runway-unlimited-plan"target="_blank" rel="noopener">plano Unlimited</a>, que sai por volta de US$ 76/mês no anual ou US$ 95/mês no mensal. &ldquo;Unlimited&rdquo; no caso significa sem cobrança por geração, mas com prioridade de fila menor em horários de pico e cota de armazenamento de saída. Pra quem precisa iterar muito, esse modelo de assinatura faz bem mais sentido que crédito avulso.</p>
<h2>A questão do preço<span class="hx:absolute hx:-mt-20" id="a-questão-do-preço"></span>
    <a href="#a-quest%c3%a3o-do-pre%c3%a7o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A precificação direta do Seedance é em crédito. O plano Standard sai por US$ 24,90/mês no anual ou US$ 49,90/mês no mensal, e te dá 2500 créditos por mês. Cada 10 segundos de vídeo gerado consome 6 créditos.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/15/seedance/pricing.png" alt="Tabela de preços do Seedance 2.0, com planos Standard até o tier mais alto e custos em crédito por segundo de geração"  loading="lazy" /></p>
<p>A conta é cruel. Com 2500 créditos no Standard, dá pra rodar cerca de 416 gerações de 10 segundos por mês. Parece muito, mas lembra do meu próprio teste: foram pelo menos duas tentativas pra chegar perto do que eu queria, e mesmo assim não foi cópia exata do que pedi. Na prática, pra cada cena que você quer entregar bem, é razoável assumir 3 a 5 regerações. Isso derruba o número útil pra algo na faixa de 80 a 140 cenas curtas por mês. Pode até dar pra um criador solo fazendo conteúdo curto. Não dá pra produção que precisa de minutos contínuos de vídeo polido.</p>
<p>Os planos superiores escalam linearmente o número de créditos, mas o problema continua: no preço de hoje, o Seedance 2.0 ainda é ferramenta pra clipe curto e meme bem-feito, ou pra prototipagem de cena antes de mandar pra estúdio de verdade. Não substitui produção profissional contínua. Pra esse perfil, na prática a Runway com Unlimited sai mais em conta. <a href="https://seedance2.ai/pricing"target="_blank" rel="noopener">Confira a tabela de preços completa aqui</a>.</p>
<h2>O que ninguém quer falar: restrição e deepfake<span class="hx:absolute hx:-mt-20" id="o-que-ninguém-quer-falar-restrição-e-deepfake"></span>
    <a href="#o-que-ningu%c3%a9m-quer-falar-restri%c3%a7%c3%a3o-e-deepfake" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tem dois pontos que eu não quero deixar de fora. O primeiro: o Seedance 2.0 demorou a chegar pro grande público em parte porque a ByteDance está sendo bem mais conservadora que a concorrência na hora de moderar. O modelo bloqueia geração com referência de pessoas reais (celebridades, políticos, figuras públicas) e filtra personagens com IP forte de Disney, Marvel/DC, Nintendo e marcas registradas em geral. Quem quiser detalhe sobre o que passa e o que não passa, e por que a empresa optou por essa postura, tem <a href="https://www.mindstudio.ai/blog/seedance-2-0-content-restrictions-workarounds"target="_blank" rel="noopener">esse artigo da MindStudio explicando os limites</a>. Faz sentido: a ByteDance opera em dezenas de jurisdições, está sob escrutínio regulatório global, e ainda tem o tema espinhoso de Hollywood pressionando o setor inteiro com ações de copyright sobre uso de material protegido em treinamento e geração. O lançamento atrasou e veio com filtro pesado justamente por isso.</p>
<p>Segundo ponto, e mais importante: pra você, leitor comum, a mensagem é que <strong>deepfake deixou de ser hipótese</strong>. Foto na internet já não vale como prova há um bom tempo, porque modelos de imagem como Nano Banana, Hunyuan e congêneres entregam realismo que engana qualquer um. Agora vídeo está na mesma categoria. O que a gente acabou de ver acima, onde com algumas referências e dois prompts dá pra gerar uma cena animada coerente em minutos, é só o começo. A pessoa comum, sem treino, não vai mais conseguir distinguir vídeo gerado de vídeo real só batendo o olho. E os filtros do Seedance só barram o uso aberto e legal. Quem quer fazer mau uso vai sempre encontrar caminho com modelo open source, com workaround, com plataforma menos rígida. Esse é o mundo real agora, e quanto antes a gente assumir isso socialmente, melhor.</p>
<h2>Não, isso não substitui artista de verdade<span class="hx:absolute hx:-mt-20" id="não-isso-não-substitui-artista-de-verdade"></span>
    <a href="#n%c3%a3o-isso-n%c3%a3o-substitui-artista-de-verdade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Já vou cortar o discurso de sempre antes que ele apareça nos comentários. Não, o Seedance não substitui artista de VFX, diretor, editor, nada disso. Isso aqui é martelo. A ferramenta é a mesma pra todo mundo. O que sai do outro lado depende inteiramente de quem está segurando ela.</p>
<p>Quem estudou enquadramento, ritmo, continuidade, direção de arte, edição, cor, composição, vai usar o Seedance pra acelerar 10 vezes o trabalho que já sabia entregar. Quem não estudou nada disso vai produzir 10 vezes mais rápido o mesmo meme de Instagram que já produzia. <strong>A IA reflete quem você é.</strong> Se você é bom artista, a ferramenta multiplica. Se você só acha que é bom, a ferramenta multiplica a mesmice. Não tem milagre.</p>
<p>E é por isso que essa onda de &ldquo;agora todo mundo vira diretor de cinema com IA&rdquo; é a mesma ladainha que ouvimos com câmera digital, com Photoshop, com After Effects, com Premiere e com qualquer ferramenta poderosa que apareceu nos últimos 30 anos. A barreira técnica cai, a barreira de gosto, repertório e estudo continua exatamente onde estava. Quem é bom fica mais produtivo. Quem não é continua precisando de gente de verdade pra fazer trabalho de verdade.</p>
<h2>Onde isso vai parar<span class="hx:absolute hx:-mt-20" id="onde-isso-vai-parar"></span>
    <a href="#onde-isso-vai-parar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Seedance 2.0 é claramente um passo na direção certa, mas ainda não é a ferramenta que substitui produção profissional. Texto sozinho continua impreciso, vídeo de referência ajuda mas ainda escapa, e o preço por minuto útil de saída polida é alto pro que entrega. A boa notícia é que a história se repete: Google já tem o Veo, Runway está com modelo próprio e ainda revendendo Seedance, Kling e Hailuo da China continuam puxando, e os modelos open source vão ganhando tração. A OpenAI tinha o Sora, mas descontinuou. Conforme a competição aperta, qualidade sobe e preço cai. Em um ou dois anos a gente deve estar olhando pra esse post e achando primitivo o que acabei de mostrar. Como sempre.</p>
]]></content:encoded><category>midia-generativa</category><category>reviews</category><category>seguranca</category></item><item><title>Como a ElevenLabs Não Foi Morta pelo Qwen3 TTS</title><link>https://akitaonrails.github.io/2026/04/09/como-a-elevenlabs-nao-foi-morta-pelo-qwen3-tts/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/04/09/como-a-elevenlabs-nao-foi-morta-pelo-qwen3-tts/</guid><pubDate>Thu, 09 Apr 2026 08:30:00 -0300</pubDate><description>&lt;p&gt;&lt;strong&gt;TL;DR — Escuta isso e continua lendo:&lt;/strong&gt;&lt;/p&gt;
&lt;audio controls preload="metadata" style="width: 100%; max-width: 640px;"&gt;
&lt;source src="https://makita-news.s3.amazonaws.com/podcasts/episodes/2026-04-06.mp3" type="audio/mpeg"&gt;
Seu navegador não suporta o elemento de áudio. &lt;a href="https://makita-news.s3.amazonaws.com/podcasts/episodes/2026-04-06.mp3"&gt;Baixar o mp3 aqui.&lt;/a&gt;
&lt;/audio&gt;
&lt;p&gt;Esse é o episódio do dia 6 de abril do podcast do &lt;a href="https://akitaonrails.github.io/tags/the-m-akita-chronicles/"&gt;The M.Akita Chronicles&lt;/a&gt;, já gerado com a nova pipeline da ElevenLabs v3. Assina o canal no &lt;a href="https://open.spotify.com/show/7MzG2UB7IAkC3GAwEXEIVD"target="_blank" rel="noopener"&gt;Spotify&lt;/a&gt; pra não perder episódio novo desses.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Quando o Qwen3 TTS foi lançado, pelos idos de janeiro desse ano, todo mundo no Twitter/X e nas newsletters de IA gritou &amp;ldquo;ElevenLabs killer&amp;rdquo;. Tem &lt;a href="https://medium.com/@warpie/qwen3-tts-is-the-first-real-open-source-threat-to-elevenlabs-56ba200ab5ee"target="_blank" rel="noopener"&gt;artigo no Medium&lt;/a&gt; dizendo que é a primeira ameaça open source real à ElevenLabs. Tem &lt;a href="https://byteiota.com/qwen3-tts-3-second-voice-cloning-beats-elevenlabs/"target="_blank" rel="noopener"&gt;post da byteiota&lt;/a&gt; dizendo que a clonagem de voz em 3 segundos bate a ElevenLabs. Tem &lt;a href="https://www.analyticsvidhya.com/blog/2025/12/qwen3-tts-flash-review/"target="_blank" rel="noopener"&gt;análise no Analytics Vidhya&lt;/a&gt; falando que é o TTS open source mais realista já lançado. O consenso da internet entusiasta era: finalmente temos open source que faz frente à ElevenLabs, o jogo virou, é só questão de tempo.&lt;/p&gt;</description><content:encoded><![CDATA[<p><strong>TL;DR — Escuta isso e continua lendo:</strong></p>
<audio controls preload="metadata" style="width: 100%; max-width: 640px;">
  <source src="https://makita-news.s3.amazonaws.com/podcasts/episodes/2026-04-06.mp3" type="audio/mpeg">
  Seu navegador não suporta o elemento de áudio. <a href="https://makita-news.s3.amazonaws.com/podcasts/episodes/2026-04-06.mp3">Baixar o mp3 aqui.</a>
</audio>
<p>Esse é o episódio do dia 6 de abril do podcast do <a href="/tags/the-m-akita-chronicles/">The M.Akita Chronicles</a>, já gerado com a nova pipeline da ElevenLabs v3. Assina o canal no <a href="https://open.spotify.com/show/7MzG2UB7IAkC3GAwEXEIVD"target="_blank" rel="noopener">Spotify</a> pra não perder episódio novo desses.</p>
<hr>
<p>Quando o Qwen3 TTS foi lançado, pelos idos de janeiro desse ano, todo mundo no Twitter/X e nas newsletters de IA gritou &ldquo;ElevenLabs killer&rdquo;. Tem <a href="https://medium.com/@warpie/qwen3-tts-is-the-first-real-open-source-threat-to-elevenlabs-56ba200ab5ee"target="_blank" rel="noopener">artigo no Medium</a> dizendo que é a primeira ameaça open source real à ElevenLabs. Tem <a href="https://byteiota.com/qwen3-tts-3-second-voice-cloning-beats-elevenlabs/"target="_blank" rel="noopener">post da byteiota</a> dizendo que a clonagem de voz em 3 segundos bate a ElevenLabs. Tem <a href="https://www.analyticsvidhya.com/blog/2025/12/qwen3-tts-flash-review/"target="_blank" rel="noopener">análise no Analytics Vidhya</a> falando que é o TTS open source mais realista já lançado. O consenso da internet entusiasta era: finalmente temos open source que faz frente à ElevenLabs, o jogo virou, é só questão de tempo.</p>
<p>Eu resolvi testar no meu próprio fluxo de produção, como de costume. Montei um pipeline inteiro em cima do Qwen3 TTS 1.7B pra gerar o podcast semanal do <a href="/tags/the-m-akita-chronicles/">The M.Akita Chronicles</a>, e documentei os bastidores no <a href="/2026/02/18/servindo-ia-na-nuvem-meu-tts-pessoal-bastidores-do-the-m-akita-chronicles/">post sobre servir IA na nuvem</a>. Quem quiser ver o detalhe de tempo de partida a frio, clonagem de voz, parâmetros de sampling que mudam de um modo pro outro, dá uma olhada nesse link que eu não vou repetir tudo aqui.</p>
<p>A pergunta desse post é diferente. Depois de quase dois meses rodando essa configuração em produção, com episódio indo ao ar toda segunda-feira, ontem à noite eu finalmente desliguei o Qwen3 e passei tudo pra ElevenLabs v3. Vou contar por quê.</p>
<h2>O que não funcionou no Qwen3<span class="hx:absolute hx:-mt-20" id="o-que-não-funcionou-no-qwen3"></span>
    <a href="#o-que-n%c3%a3o-funcionou-no-qwen3" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Entre 15 de fevereiro e 30 de março eu fiz dezenas de commits ajustando o fluxo do podcast: prompts, parâmetros de sampling, ordem de geração, silêncios de referência na amostra de clone de voz, normalização de volume, pronúncia de siglas técnicas. Corrigir a voz do Marvin que cortava a primeira sílaba porque o áudio de referência começava sem silêncio inicial. Afinar a voz do Akita pra ela soar mais confiante e assertiva. Adicionar crossfade entre jingles de seção. Resolver a pronúncia errada de &ldquo;podcast&rdquo; pra ela não sair como &ldquo;pódcast&rdquo;. Mandar o gerador de roteiro preferir português a anglicismo gratuito pro TTS não engasgar. Cada um desses ajustes foi uma sessão de horas escutando áudio, gerando de novo, ajustando parâmetro.</p>
<p>O resultado ficou aceitável. &ldquo;Aceitável&rdquo; no sentido de que eu consegui publicar todo episódio sem ter que regravar nada à mão. Mas escutando com atenção, a voz do Qwen3 tem aquele jeito inconfundível de IA gerando áudio. Intonação meio morta, ritmo uniforme. Em trechos longos, você sente que é máquina falando. Serve pra ir pro ar, mas tá a quilômetros do que você escuta num podcast profissional feito por gente.</p>
<p>O problema pior foi a pronúncia em inglês. Meu podcast cobre notícias de tecnologia, então termos como &ldquo;MCP&rdquo;, &ldquo;RAG&rdquo;, &ldquo;Claude Opus&rdquo;, &ldquo;GPT-5&rdquo;, &ldquo;open source&rdquo; aparecem em toda conversa. O Qwen3 pegava esses termos e pronunciava eles com sotaque brasileiro, tipo &ldquo;ó-péne-ssourssê&rdquo;, coisa assim. Ficava ilegível pro ouvinte. A solução que eu tive que implementar foi mapear manualmente no prompt do LLM que gera o roteiro quais palavras em inglês trocar por equivalente em português. O prompt hoje tem uma seção inteira dividida entre &ldquo;manter em inglês&rdquo; (nomes próprios, marcas, termos já incorporados ao brasileiro) e &ldquo;traduzir pro português&rdquo; (anglicismo gratuito), mais ou menos assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">**REPLACE with Portuguese** (common English words that have natural
</span></span><span class="line"><span class="cl">Brazilian Portuguese equivalents):
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;update&#34; → &#34;atualização&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;release&#34; → &#34;lançamento&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;feature&#34; → &#34;recurso/funcionalidade&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;deploy&#34; → &#34;implantação&#34; or just &#34;colocar em produção&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;trade-off&#34; → &#34;dilema&#34; or &#34;escolha&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;performance&#34; → &#34;desempenho&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;default&#34; → &#34;padrão&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;insight&#34; → &#34;percepção/sacada&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;skills&#34; → &#34;habilidades&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;approach&#34; → &#34;abordagem&#34;
</span></span><span class="line"><span class="cl">- &#34;highlights&#34; → &#34;destaques&#34;</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Chamam isso aqui em casa de &ldquo;limpar anglicismo pra o TTS não engasgar&rdquo;. Engraçado porque eu não queria esse nível de restrição no meu roteiro. Queria que a voz simplesmente pronunciasse &ldquo;update&rdquo; quando o contexto natural fosse &ldquo;update&rdquo;. Como o modelo não consegue, tive que mutilar o vocabulário do podcast pra o resultado final ficar escutável. É uma solução paliativa, daquelas que você adiciona torcendo pra poder remover depois quando a tecnologia amadurecer.</p>
<h2>A experiência com a ElevenLabs v3<span class="hx:absolute hx:-mt-20" id="a-experiência-com-a-elevenlabs-v3"></span>
    <a href="#a-experi%c3%aancia-com-a-elevenlabs-v3" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Ontem à tarde abri uma conta na ElevenLabs, comprei o plano Pro (US$99/mês), e comecei a experimentar com o modelo <code>eleven_v3</code>, que saiu em fevereiro desse ano. Trinta minutos depois eu tinha uma prova de conceito rodando, e umas duas horas depois o sistema inteiro do podcast migrou. A diferença de esforço é abissal.</p>
<p>Os detalhes técnicos da migração ficaram documentados num doc interno do projeto, então vou resumir aqui o quadro comparativo que importa:</p>
<table>
  <thead>
      <tr>
          <th>Dimensão</th>
          <th>Qwen3 TTS 1.7B (antigo)</th>
          <th>ElevenLabs <code>eleven_v3</code> (atual)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Qualidade (Akita)</td>
          <td>Boa, clone de áudio real</td>
          <td>Melhor, mesmo clone mas prosódia mais natural</td>
      </tr>
      <tr>
          <td>Emoção inline no roteiro</td>
          <td>Não suporta</td>
          <td><code>[sighs]</code>, <code>[sarcastically]</code>, <code>[excited]</code>, <code>[laughs]</code>, funciona em pt-BR</td>
      </tr>
      <tr>
          <td>Tempo de partida a frio</td>
          <td>5 a 15 min subindo GPU na RunPod antes de cada rodagem</td>
          <td>Zero, chamada HTTPS com resposta imediata</td>
      </tr>
      <tr>
          <td>Vazão</td>
          <td>~1× do tempo real (serializado)</td>
          <td>~6× do tempo real com concorrência 4</td>
      </tr>
      <tr>
          <td>Tempo total pra um episódio de 28 min</td>
          <td>~25 a 30 min</td>
          <td><strong>~4 min</strong></td>
      </tr>
      <tr>
          <td>Superfície operacional</td>
          <td>RunPod, Docker, FastAPI, pesos do Qwen, conta de GPU</td>
          <td>Uma variável de ambiente (<code>ELEVENLABS_API_KEY</code>)</td>
      </tr>
      <tr>
          <td>Custo por episódio</td>
          <td>~$0.08 de GPU</td>
          <td>~$2.70 em créditos ElevenLabs</td>
      </tr>
  </tbody>
</table>
<p>Repara no último ponto. O Qwen3 custa menos de dez centavos de dólar por episódio. A ElevenLabs custa quase trinta vezes mais. E mesmo assim vale a pena. Os outros pontos do quadro resolvem problemas que sugavam horas da minha semana. Eu não preciso mais escrever código pra escalar GPU na nuvem, não preciso esperar a máquina ligar toda vez, não preciso ficar de babá do modelo nem reconstruir a imagem Docker quando o peso muda. A operação virou uma linha de configuração.</p>
<p>E o mais interessante: as tags de emoção no meio do texto. O modelo v3 aceita marcadores tipo <code>[sighs]</code>, <code>[sarcastically]</code>, <code>[dryly]</code>, <code>[excited]</code>, e muda a entonação de acordo. Isso funciona em mais de 70 idiomas, incluindo português do Brasil. Isso transformou a geração do roteiro porque agora eu posso pedir ao LLM que monta o roteiro pra colocar tags emocionais nos momentos certos, o que dá uma vivacidade que o Qwen3 não conseguia entregar nem de longe. Um exemplo concreto do que sai no roteiro depois:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>AKITA: Isso é simples. [dismissive] Quem ainda acredita que Bitcoin
vai morrer não tá prestando atenção.
MARVIN: [sighs] Mais uma semana, mais uma leva de devs confiando
cegamente em pacotes npm. Previsível.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Eu tenho até duas paletas separadas de tags, uma pro Akita (expressivo mas controlado, usa <code>[excited]</code>, <code>[dismissive]</code>, <code>[emphatic]</code>) e outra pro Marvin (estóico, só <code>[sighs]</code>, <code>[sarcastically]</code>, <code>[tired]</code>, <code>[dryly]</code>). Isso tá tudo codificado nos prompts de geração do roteiro pro LLM saber que personagem pode usar o quê.</p>
<h2>Sobre a voz do Marvin<span class="hx:absolute hx:-mt-20" id="sobre-a-voz-do-marvin"></span>
    <a href="#sobre-a-voz-do-marvin" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pros ouvintes que já se acostumaram com a voz do Marvin, fica tranquilo: eu fiz o clone dele na ElevenLabs usando a mesma amostra de áudio que eu já tinha usado pra treinar no Qwen3. É a mesma voz. Só que agora ela soa ainda melhor, porque o modelo da ElevenLabs captura nuance e prosódia que o Qwen3 não conseguia entregar.</p>
<h2>Escuta aí e me conta<span class="hx:absolute hx:-mt-20" id="escuta-aí-e-me-conta"></span>
    <a href="#escuta-a%c3%ad-e-me-conta" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra provar que o papo é real, aqui tá o episódio de segunda-feira, dia 6 de abril, já gerado com a nova pipeline:</p>
<audio controls preload="metadata" style="width: 100%; max-width: 640px;">
  <source src="https://makita-news.s3.amazonaws.com/podcasts/episodes/2026-04-06.mp3" type="audio/mpeg">
  Seu navegador não suporta o elemento de áudio. <a href="https://makita-news.s3.amazonaws.com/podcasts/episodes/2026-04-06.mp3">Baixar o mp3 aqui.</a>
</audio>
<p>Se você já é ouvinte do podcast no <a href="https://open.spotify.com/show/7MzG2UB7IAkC3GAwEXEIVD"target="_blank" rel="noopener">Spotify</a> e escutou os episódios anteriores feitos com Qwen3, compara e me diz nos comentários se você nota a diferença ou se pra você tanto faz. Eu tô curioso de saber quanto é percepção treinada minha escutando horas de áudio de TTS e quanto é diferença óbvia pra ouvinte casual.</p>
<p>A partir da próxima semana, todos os episódios do podcast serão gerados pela ElevenLabs v3. A newsletter já tá ligada na nova pipeline, os jobs agendados de pré-aquecer a GPU na RunPod foram desativados, e o código legado do Qwen3 fica no repositório como plano B caso um dia o v3 dê problema crônico. Em duas edições de arquivo eu volto pra ele. Provavelmente nunca vou ter que voltar.</p>
<h2>A parte de dublar os vídeos do YouTube<span class="hx:absolute hx:-mt-20" id="a-parte-de-dublar-os-vídeos-do-youtube"></span>
    <a href="#a-parte-de-dublar-os-v%c3%addeos-do-youtube" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora o gancho pra segunda metade desse post. No <a href="/2026/04/09/20-anos-de-blog-o-ano-em-que-a-ia-finalmente-me-deixou-traduzir-tudo/">artigo de aniversário que eu publiquei mais cedo hoje</a>, contei que o Claude Code traduziu quase metade do meu blog pra inglês num fim de semana. No mesmo espírito, fui atrás de dublar os vídeos do canal <a href="https://www.youtube.com/@Akitando"target="_blank" rel="noopener">Akitando</a>.</p>
<p>O canal tem 146 episódios, algo como 96 horas de conteúdo técnico em português, e mais de 500 mil inscritos. Eu já tinha as legendas traduzidas (um <code>.srt</code> curado por episódio), e o YouTube até oferece dublagem automática em inglês. Mas o resultado é tipo Google Translate de 2015: entende, passa a ideia, mas ninguém quer ouvir por muito tempo.</p>
<p>Testei as três abordagens de voz da ElevenLabs e só uma servia. Speech-to-Speech converte voz mas não traduz. A API de Dubbing traduz mas cria a voz sozinha, sem deixar forçar um clone específico. Só a Text-to-Speech resolvia: pegar meu <code>.srt</code> em inglês, mandar cada bloco pro endpoint TTS com a minha voz clonada, e montar o áudio alinhado com o vídeo original.</p>
<p>Só que ter o <code>.srt</code> traduzido não basta. Tradução crua de legenda não sobrevive ao TTS. Trechos com código-fonte, URLs, hashes hexadecimais, listas de comandos shell — o modelo entra em modo soletrar e o áudio sai com o dobro da duração esperada. Traduções longas demais estouram a janela de tempo e precisam ser condensadas pra a voz não ficar atropelada. SRTs truncadas precisam ser completadas. E a cada correção, rodar a pipeline de novo, escutar, achar o próximo problema, corrigir, repetir. O processo inteiro foi um ciclo de interrupções, correções manuais e re-runs — longe do &ldquo;aperta o botão e sai dublado&rdquo; que a gente imagina antes de meter a mão.</p>
<p>O grande desafio era o sotaque. Minha voz clonada foi treinada em português brasileiro, então quando tenta falar inglês o sotaque puxado aparece. A ElevenLabs tem a tag <code>[American accent]</code> que funciona em v3, mas em cima de uma voz treinada em outra língua ela é fraca — o sotaque brasileiro ainda saía por baixo. A saída foi treinar uma segunda voz minha, só em inglês. Gravei uns minutos no meu melhor sotaque americano, subi como Instant Voice Clone separado na minha conta, batizei de &ldquo;Akita English&rdquo;, e configurei o pipeline pra usar essa voz por padrão. O resultado sai mais natural, sem precisar de tag nenhuma, e a identidade de voz continua sendo a minha.</p>
<h3>O teto honesto de qualidade da minha voz em inglês<span class="hx:absolute hx:-mt-20" id="o-teto-honesto-de-qualidade-da-minha-voz-em-inglês"></span>
    <a href="#o-teto-honesto-de-qualidade-da-minha-voz-em-ingl%c3%aas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Vamos ser francos. Eu sou brasileiro falando inglês, não sou nativo, e não tenho tempo nem paciência pra treinar pronúncia por horas. O clone só consegue ser tão bom quanto a amostra original — se a amostra é um não-nativo lendo um roteiro, o clone herda as imperfeições. Nenhum clone vai me fazer soar como &ldquo;Fabio falando inglês perfeito&rdquo;, porque esse som não existe pra o modelo imitar.</p>
<p>Pra chegar na versão que tá rodando, gravei cinco amostras de treino diferentes ao longo do dia, cada uma com ritmo e roteiro diferentes, e rodei um A/B ao vivo comparando com o original em português. Nenhuma soou como nativo, e nunca foi esse o objetivo. A meta realista era mais modesta: soar reconhecivelmente como eu, ler conteúdo técnico sem tropeçar, e aguentar um episódio inteiro sem cansar o ouvinte. O vencedor acabou sendo a primeira iteração, a &ldquo;Akita English&rdquo; original. Longe de perfeito, mas suficiente pra colocar no ar. Trocar no futuro é literalmente uma linha no arquivo de configuração.</p>
<p>O que mais me surpreendeu no caminho foi perceber que o ritmo da amostra importa mais que o timbre. O clone aprende a cadência de quem gravou: a iteração 5 ficou 25% mais lenta que a iteração 2 lendo exatamente o mesmo texto, só porque eu gravei aquela amostra mais devagar. Pra dublagem de vídeo tech, o alvo certo é ritmo de explicação de YouTube, vivo, uns 150 palavras por minuto. Nada de ritmo de audiobook.</p>
<h3>Como eu alinho o áudio com o vídeo original<span class="hx:absolute hx:-mt-20" id="como-eu-alinho-o-áudio-com-o-vídeo-original"></span>
    <a href="#como-eu-alinho-o-%c3%a1udio-com-o-v%c3%addeo-original" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Um ponto não óbvio: texto em inglês tende a ser mais longo ao falar do que o português equivalente. Se você simplesmente gera o áudio de cada legenda e cola no tempo original, o dub vai se acumulando. Com 30 minutos de vídeo, você já tá vários segundos fora de sincronismo.</p>
<p>Minha abordagem foi atacar o problema em camadas. A primeira versão dividia o SRT em blocos menores, chamados de &ldquo;chunks&rdquo;. Cada chunk tinha no máximo 700 caracteres (pra v3 não alucinar) e só podia cortar em fim de frase, nunca no meio. Um algoritmo simples acumulava legendas num buffer até chegar perto do teto, depois voltava procurando a última legenda do buffer que terminava com ponto, exclamação ou interrogação, e fazia flush só até ali. O resto das legendas já acumuladas ficava pra compor o próximo chunk. Cada chunk guardava o timestamp de início e fim das legendas que cobria, pra a montagem saber exatamente onde posicionar aquele pedaço de áudio depois.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Pra cada nova legenda que chega, checa se adicionar ela</span>
</span></span><span class="line"><span class="cl"><span class="c1"># estouraria o limite do chunk atual.</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">cue</span> <span class="ow">in</span> <span class="n">cues</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">projected</span> <span class="o">=</span> <span class="n">buffer_char_len</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="n">cue</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">projected</span> <span class="o">&gt;</span> <span class="n">max_chars</span> <span class="ow">and</span> <span class="n">buf</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Procura a última legenda do buffer que termina</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># com pontuação final (&#39;.&#39;, &#39;!&#39;, &#39;?&#39;, &#39;…&#39;).</span>
</span></span><span class="line"><span class="cl">        <span class="n">sentence_end</span> <span class="o">=</span> <span class="n">last_sentence_end_index</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">sentence_end</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">buf</span> <span class="o">=</span> <span class="n">flush</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">sentence_end</span><span class="p">,</span> <span class="n">chunks</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Run-on maior que max_chars sem fim de frase.</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Raro, mas acontece. Avisa e corta mesmo assim.</span>
</span></span><span class="line"><span class="cl">            <span class="n">log</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s2">&#34;run-on sentence &gt; </span><span class="si">%d</span><span class="s2"> chars — &#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;splitting mid-sentence as a last resort&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">max_chars</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">buf</span> <span class="o">=</span> <span class="n">flush</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span> <span class="n">chunks</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">cue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Soft-break: se o buffer já tá pelo menos 60% cheio E</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># a legenda atual terminou uma frase, esvazia agora.</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Mantém os chunks balanceados em torno de 60 a 100% do max.</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">cue</span><span class="o">.</span><span class="n">ends_sentence</span> <span class="ow">and</span> <span class="n">buffer_char_len</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">max_chars</span> <span class="o">*</span> <span class="mf">0.6</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">buf</span> <span class="o">=</span> <span class="n">flush</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span> <span class="n">chunks</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tecnicamente, esse foi o ponto de virada: em vez de deixar a estrutura da dublagem seguir a estrutura visual da legenda, o chunker passou a seguir a estrutura real dos parágrafos do roteiro. Não vou repetir aqui toda a novela de como cheguei nisso e por que isso acabou forçando rerender em massa, porque volto nessa história mais abaixo, na parte dos problemas que só apareceram depois do primeiro upload. O ponto técnico aqui é simples: quando a janela passou a representar um parágrafo falado de verdade, o áudio começou a soar natural e o alvo de stretch pôde subir de 92% pra 95%.</p>
<p>Feito isso, ainda sobra o problema do inglês ser mais longo ao falar que o português equivalente. Aqui a sacada é prever se um chunk vai estourar a janela alvo <strong>antes</strong> de gerar o áudio. Se a razão <code>caracteres / 16 caracteres-por-segundo</code> já passa da duração alvo com uma margem, a pipeline manda o parâmetro <code>speed</code> direto pra API da ElevenLabs, pedindo pro modelo gerar o áudio nativamente mais rápido. Isso preserva a prosódia muito melhor do que comprimir depois. O teto é 1.15× (acima disso a voz começa a soar atropelada).</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># antes de chamar a API, prevê se vai estourar</span>
</span></span><span class="line"><span class="cl"><span class="n">expected_sec</span> <span class="o">=</span> <span class="n">char_count</span> <span class="o">/</span> <span class="n">EXPECTED_CHARS_PER_SEC</span>  <span class="c1"># 16 chars/s</span>
</span></span><span class="line"><span class="cl"><span class="n">predicted_ratio</span> <span class="o">=</span> <span class="n">expected_sec</span> <span class="o">/</span> <span class="n">target_sec</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">voice_speed</span> <span class="o">=</span> <span class="mf">1.0</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">predicted_ratio</span> <span class="o">&gt;</span> <span class="n">PREEMPTIVE_SPEED_THRESHOLD</span><span class="p">:</span>   <span class="c1"># 1.05</span>
</span></span><span class="line"><span class="cl">    <span class="n">voice_speed</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="n">predicted_ratio</span> <span class="o">*</span> <span class="mf">0.98</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                      <span class="n">PREEMPTIVE_SPEED_MAX</span><span class="p">)</span>         <span class="c1"># cap em 1.15</span>
</span></span><span class="line"><span class="cl">    <span class="n">voice_settings</span><span class="p">[</span><span class="s2">&#34;speed&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">voice_speed</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mesmo com o <code>speed</code> preventivo ligado, às vezes o áudio gerado ainda fica um pouquinho mais longo do que a janela alvo. Pra isso existe uma segunda rede de proteção: medir o áudio real com <code>ffprobe</code> depois de gerado, comparar com a janela alvo, e aplicar o filtro <code>atempo</code> do <code>ffmpeg</code> pra comprimir no pós-processo se passou de 2% de tolerância, com teto de 1.20×. A combinação do <code>speed</code> nativo (1.15×) com o <code>atempo</code> (1.20×) dá uma compressão efetiva de até 1.38×, suficiente pra caber naturalmente mesmo nos casos mais extremos sem quebrar qualidade.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># depois de gerar o chunk</span>
</span></span><span class="line"><span class="cl"><span class="n">actual</span> <span class="o">=</span> <span class="n">ffprobe_duration</span><span class="p">(</span><span class="n">chunk_path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ratio</span> <span class="o">=</span> <span class="n">actual</span> <span class="o">/</span> <span class="n">target_sec</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">ratio</span> <span class="o">&gt;</span> <span class="n">FIT_TOLERANCE</span><span class="p">:</span>       <span class="c1"># 1.02</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># comprime o áudio sem mexer no pitch</span>
</span></span><span class="line"><span class="cl">    <span class="n">ffmpeg_atempo</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="n">ratio</span><span class="p">,</span> <span class="n">FIT_MAX_ATEMPO</span><span class="p">)</span>   <span class="c1"># 1.20</span>
</span></span><span class="line"><span class="cl">    <span class="n">apply_atempo</span><span class="p">(</span><span class="n">chunk_path</span><span class="p">,</span> <span class="n">ffmpeg_atempo</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Quando um chunk gerado fica mais curto que a janela alvo, o pipeline estende o áudio levemente (sem afetar pitch) pra reduzir o silêncio feio depois que ele termina. O objetivo não é preencher a janela inteira, uma pausa natural entre frases é boa, mas suavizar os casos mais gritantes de silêncio que dariam a impressão de &ldquo;áudio cortado&rdquo;.</p>
<p>Num episódio grande, 95 minutos, 194 chunks, o desvio cumulativo total ficou em -0,7%. Uns 43 segundos de drift ao longo do episódio inteiro, imperceptível enquanto você assiste.</p>
<p>O que salvou o orçamento nessa arquitetura é que cada chunk fica salvo individualmente no disco como um <code>.mp3</code> separado. A pipeline mantém um manifesto com o texto normalizado de cada chunk, e antes de chamar a API da ElevenLabs, compara o texto atual com o cache. Se o texto não mudou, reutiliza o áudio já gerado sem gastar crédito nenhum. Se eu reescrevo uma cue problemática, só os chunks afetados por aquela cue são regenerados — o resto do episódio fica intacto.</p>
<p>Isso é o que viabilizou as iterações. Eu rodava o batch, escutava trechos, identificava um problema (tradução longa demais, snippet de código que o TTS não conseguia pronunciar, chunking que cortou num ponto ruim), corrigia o SRT ou ajustava os parâmetros do chunker, e rodava de novo. Cada re-run consumia uma fração dos créditos e do tempo do run original, porque só os chunks alterados eram regenerados. Sem esse cache, cada iteração teria custado quase o mesmo que a primeira rodada, e o custo total do batch teria sido duas ou três vezes maior.</p>
<p>Essa mudança de chunking também teve impacto pesado no cache e no custo, mas deixo essa parte pro trecho mais abaixo onde explico a sequência de retrabalho. Aqui basta dizer que, do ponto de vista técnico, era a mudança certa.</p>
<h3>Quando o <code>atempo</code> não salva: reescrevendo cue antes do TTS<span class="hx:absolute hx:-mt-20" id="quando-o-atempo-não-salva-reescrevendo-cue-antes-do-tts"></span>
    <a href="#quando-o-atempo-n%c3%a3o-salva-reescrevendo-cue-antes-do-tts" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Nem tudo é questão de alinhamento de janela. Tem um tipo de cue que quebra o pipeline inteiro, onde nem <code>speed</code> preventivo nem <code>atempo</code> no pós-processo resolvem, e eu só esbarrei nisso quando comecei a rodar o batch nos episódios mais técnicos do canal.</p>
<p>O problema é o seguinte. O v3 lê a uns 16 caracteres por segundo quando você entrega texto normal de conversa. Mas quando você entrega uma cue recheada de URL literal, hash hexadecimal, string binária, uma lista de números ordenados ou um bloco de código shell, o modelo entra em modo &ldquo;soletrar letra por letra&rdquo; e despenca pra uns 9 caracteres por segundo. Uma cue de 500 caracteres que devia virar 30 segundos de áudio vira 55. A checagem de sanidade derruba (porque passou de 1.8×), o retry automático tenta de novo com os mesmos 500 caracteres, os cinco retries falham seguidos, e o chunk fica preso.</p>
<p>E isso não aconteceu por acaso. Eu escrevia desse jeito porque sabia que esses mesmos roteiros depois virariam post no blog, então fazia sentido deixar a URL crua, o comando completo, o hash inteiro, o detalhe técnico todo mastigado pro leitor. Em português isso funcionava bem porque era exatamente o meu fluxo original: gravar o vídeo e depois publicar o texto correspondente no blog. O que eu nunca tinha preparado era a etapa seguinte, de transformar esse mesmo material em dublagem automatizada pra inglês. Eu só fui perceber esse conflito quando a pipeline começou a falhar de verdade.</p>
<p>Peguei isso primeiro no ep052, o guia de Ubuntu pra devs iniciantes, onde duas cues traziam URLs do <code>github.com</code>, URLs <code>hkp://keyserver.ubuntu.com</code> e um hash GPG de 40 caracteres. Tentar resolver no pós-processo seria perda de tempo. O teto de 1.20× do <code>atempo</code> nunca vai comprimir 55 segundos em 30, e mesmo se conseguisse ia sair num chipmunk ilegível. A saída é atacar antes, no texto.</p>
<p>A solução foi reescrever a cue dizendo o que o comando faz, em vez de mostrar o comando literal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gd">- Rode: apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 \
</span></span></span><span class="line"><span class="cl"><span class="gd">-   --recv-keys 0A6A3E7F79F93EF8AAB9E92BAEBB74C8B5A1E44D
</span></span></span><span class="line"><span class="cl"><span class="gi">+ Rode o comando completo que tá na descrição do vídeo pra importar
</span></span></span><span class="line"><span class="cl"><span class="gi">+ a chave de assinatura do repositório do keyserver do Ubuntu.
</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A legenda em inglês no vídeo continua mostrando o comando inteiro na tela, então quem lê a legenda vê o que precisa digitar. O áudio dublado descreve a intenção em linguagem falada, que é o que o TTS entrega sem engasgar. A instrução pro ouvinte fica intacta, e o caractere-por-segundo volta pros 16 esperados, então o áudio gerado cabe na janela original sem precisar esticar nem comprimir nada no pós.</p>
<p>Saí caçando cue assim em todos os episódios do batch. Varri do ep052 ao ep146 procurando cues com densidade alta de caractere &ldquo;duro&rdquo; (dígitos, colchetes, operadores, URLs longas, binário, hex) e acabei reescrevendo umas 30 cues em 13 episódios:</p>
<ul>
<li>ep052 Ubuntu pra devs: URLs do GitHub e hash GPG de 40 caracteres</li>
<li>ep091 Hello World em C: exemplos binários</li>
<li>ep095 Memória 640kB: wrap de endereço, segment-offset</li>
<li>ep106 CodeMiner: endereço de e-mail soletrado</li>
<li>ep113 Compressão: strings de binário puro, 75% de caractere duro</li>
<li>ep115 SQL Server: lista longa de números ordenados</li>
<li>ep120 Internet: endereços IP com pontos</li>
<li>ep121 Sockets: dois blocos de código JavaScript</li>
<li>ep122 Proxies: headers User-Agent do Chrome</li>
<li>ep123 Rede segura: one-liners shell, flags do Docker</li>
<li>ep126 Gentoo: demo de <code>chroot</code> em C</li>
<li>ep136 Containers: URL de release do GitHub</li>
<li>ep144 Criptografia: endereço de signing key</li>
</ul>
<p>O padrão foi sempre o mesmo: mantém a legenda exibindo o código, a URL ou o hash na íntegra, e reescreve o texto pro narrador descrever o que aquilo faz. Eu podia ter automatizado a reescrita, deixando um LLM ler a cue e propor uma substituição no voo, só que achei mais seguro revisar tudo manualmente. É exatamente o tipo de coisa onde o modelo resolve &ldquo;melhorar&rdquo; um comando pra deixá-lo mais limpo e sai com shell errado. A varredura manual levou umas duas horas. A automática teria custado o mesmo tempo em revisão, com o bônus da ansiedade de ter deixado passar algum comando mutilado.</p>
<p>Teve também um caso estrutural esquisito no ep146, sobre Docker Compose. Duas cues ficaram grudadas por causa de uma linha em branco faltando no SRT, e o <code>pysrt</code> tratava as duas como uma única cue gigante. O TTS nem chegava a processar, o chunker engasgava antes. Corrigi à mão adicionando a linha em branco que faltava. Fix de um caractere, meia hora pra rastrear.</p>
<h3>Reconstruindo SRTs truncadas<span class="hx:absolute hx:-mt-20" id="reconstruindo-srts-truncadas"></span>
    <a href="#reconstruindo-srts-truncadas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Outra armadilha apareceu quando fui rodar o batch completo: três episódios tinham legendas em inglês curtas demais em relação à duração real do vídeo. O ep056, sobre Rails, era o caso mais absurdo. Dezenove minutos de legenda pra um vídeo de 80 minutos. Sessenta e dois minutos de conteúdo sem legenda nenhuma. O ep057 (WSL 2) tinha 14 minutos faltando, e o ep068 (Git Direito) tinha 2.</p>
<p>Isso é rastro do meu fluxo de tradução antigo. Em algum momento eu comecei a revisar a legenda manualmente, parei no meio, e o arquivo <code>.en.srt</code> ficou salvo truncado no ponto onde eu parei. Não dá pra dublar um vídeo de 80 minutos com legenda de 19. O chunker gera áudio até onde consegue ler e simplesmente não sabe o que fazer com o resto do vídeo.</p>
<p>A solução virou um script novo. Ele faz diff entre o <code>.en.srt</code> truncado e o <code>.pt-orig.srt</code> (o auto-caption bruto do YouTube, que sempre cobre o vídeo inteiro), pega as cues que existem só em português, manda pro Claude Sonnet 4.6 com um schema JSON rígido pedindo tradução cue a cue, e cola o resultado de volta no fim do arquivo em inglês. O schema é o detalhe que importa. Quando eu tentei pedir a resposta em texto no formato <code>N|texto</code>, o Claude deixava cair umas 20% das cues no caminho. Com um schema JSON estrito, a taxa de drop caiu pra zero nos três reparos.</p>
<p>O resultado em números:</p>
<ul>
<li>ep068 Git Direito: +50 cues / 2 minutos reconstruídos</li>
<li>ep057 WSL 2: +368 cues / 14 minutos</li>
<li>ep056 Rails: +1445 cues / 62 minutos (e ainda drop de 5 cues falsas que o tradutor antigo tinha inventado no fim pra tampar a saída)</li>
</ul>
<p>Depois dos reparos, os três episódios entraram no batch normalmente e foram dublados como qualquer outro. Tipo de ferramenta que você espera nunca precisar, só que quando precisa, compensa escrever uma vez e resolver o problema inteiro de uma vez.</p>
<h3>Tags de emoção automáticas<span class="hx:absolute hx:-mt-20" id="tags-de-emoção-automáticas"></span>
    <a href="#tags-de-emo%c3%a7%c3%a3o-autom%c3%a1ticas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Enquanto eu tava testando, resolvi adicionar mais um passo no pipeline: um &ldquo;emotion tagger&rdquo; que lê o SRT em inglês antes de mandar pro TTS e insere tags tipo <code>[sarcastic]</code>, <code>[thoughtful]</code>, <code>[emphatic]</code>, <code>[deadpan]</code> em pontos onde um narrador humano naturalmente mudaria o tom. A ideia é imitar o que um ator profissional faria, colocar ênfase nos momentos certos, sem transformar o vídeo num teatro de emoções exageradas.</p>
<p>Essa parte é delicada por dois motivos. O primeiro: deixar um LLM solto editando o SRT tem risco real de ele &ldquo;melhorar&rdquo; o texto (trocar uma palavra aqui, reescrever uma frase ali) e você acabar com uma dublagem que não bate com a legenda. O segundo: LLM adora exagerar. Se você pedir pra marcar emoção, ele vai colocar tag em toda segunda frase. Pra evitar os dois, eu fixei uma lista pequena de tags permitidas e rodo uma validação de round-trip depois que a resposta do Claude chega:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ALLOWED_TAGS</span><span class="p">:</span> <span class="nb">frozenset</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="nb">frozenset</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;[sarcastic]&#34;</span><span class="p">,</span> <span class="s2">&#34;[thoughtful]&#34;</span><span class="p">,</span> <span class="s2">&#34;[emphatic]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;[deadpan]&#34;</span><span class="p">,</span> <span class="s2">&#34;[serious]&#34;</span><span class="p">,</span> <span class="s2">&#34;[amused]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;[sighs]&#34;</span><span class="p">,</span> <span class="s2">&#34;[exasperated]&#34;</span><span class="p">,</span> <span class="s2">&#34;[confident]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;[matter-of-fact]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">validate_tagged</span><span class="p">(</span><span class="n">original</span><span class="p">,</span> <span class="n">tagged</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Garante que o SRT com tags preserva o original sem drift.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">original</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">len</span><span class="p">(</span><span class="n">tagged</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">raise</span> <span class="n">TagValidationError</span><span class="p">(</span><span class="s2">&#34;cue count mismatch&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">o</span><span class="p">,</span> <span class="n">t</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">original</span><span class="p">,</span> <span class="n">tagged</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># O index e o timestamp têm que bater byte a byte.</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">o</span><span class="o">.</span><span class="n">index</span> <span class="o">!=</span> <span class="n">t</span><span class="o">.</span><span class="n">index</span> <span class="ow">or</span> <span class="n">o</span><span class="o">.</span><span class="n">timestamp</span> <span class="o">!=</span> <span class="n">t</span><span class="o">.</span><span class="n">timestamp</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="n">TagValidationError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;cue </span><span class="si">{</span><span class="n">o</span><span class="o">.</span><span class="n">index</span><span class="si">}</span><span class="s2">: header drift&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Qualquer tag fora da whitelist invalida tudo.</span>
</span></span><span class="line"><span class="cl">        <span class="n">bad</span> <span class="o">=</span> <span class="n">find_disallowed_tags</span><span class="p">(</span><span class="n">t</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">bad</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="n">TagValidationError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;cue </span><span class="si">{</span><span class="n">o</span><span class="o">.</span><span class="n">index</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">bad</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># O texto sem as tags tem que ser idêntico ao original.</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Se o LLM trocou uma palavra, reescreveu algo, a</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># validação quebra e a resposta é rejeitada.</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">strip_tags</span><span class="p">(</span><span class="n">o</span><span class="o">.</span><span class="n">text</span><span class="p">)</span> <span class="o">!=</span> <span class="n">strip_tags</span><span class="p">(</span><span class="n">t</span><span class="o">.</span><span class="n">text</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="n">TagValidationError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;cue </span><span class="si">{</span><span class="n">o</span><span class="o">.</span><span class="n">index</span><span class="si">}</span><span class="s2">: text drift&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Quando a validação quebra, a pipeline ignora a resposta e reemite o request (ou, se falhar muito, sobe o SRT sem tags mesmo). Sobre a quantidade de tags, o prompt que vai pro Claude diz explicitamente: mira em <code>N/10</code> tags pra um SRT de N legendas, chão de 1 tag a cada 12, teto de 1 tag a cada 7, distribuição equilibrada entre os quatro quartos do episódio, nunca duas tags adjacentes a menos de 4 legendas uma da outra. Esse conjunto de regras foi a sexta iteração — as cinco anteriores ou enchiam tudo de tag ou concentravam no começo do vídeo.</p>
<h3>Não deixe a v3 alucinar<span class="hx:absolute hx:-mt-20" id="não-deixe-a-v3-alucinar"></span>
    <a href="#n%c3%a3o-deixe-a-v3-alucinar" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Um detalhe crítico que só aparece em produção: a ElevenLabs v3 tende a alucinar quando você manda blocos de texto muito longos. Já peguei blocos onde o texto tinha uns 1.500 caracteres e o modelo gerou um áudio com <strong>nove minutos</strong> quando o esperado era um minuto e meio. O modelo simplesmente resolve continuar falando sozinho, inventando conteúdo.</p>
<p>A documentação oficial da ElevenLabs recomenda manter cada chamada abaixo de 800 caracteres pra evitar isso. Eu fui mais conservador e cortei em 700, sempre em fim de frase (nunca no meio de uma sentença, porque o corte no meio soa horrível quando concatena). Depois disso, subi o parâmetro <code>stability</code> pra 0.9 (modo Robust, mais estável porém menos responsivo a tags de emoção), liguei o <code>apply_text_normalization</code> pra o modelo pronunciar números e siglas direito, e adicionei uma verificação de sanidade que rejeita qualquer áudio gerado que passe de 1.8× da duração esperada.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">DEFAULT_VOICE_SETTINGS</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;stability&#34;</span><span class="p">:</span> <span class="mf">0.9</span><span class="p">,</span>          <span class="c1"># modo Robust</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;similarity_boost&#34;</span><span class="p">:</span> <span class="mf">0.95</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;style&#34;</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;use_speaker_boost&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">EXPECTED_CHARS_PER_SEC</span> <span class="o">=</span> <span class="mf">16.0</span>
</span></span><span class="line"><span class="cl"><span class="n">CHUNK_SANITY_MAX_FACTOR</span> <span class="o">=</span> <span class="mf">1.8</span>   <span class="c1"># rejeita se actual &gt; 1.8× esperado</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Depois dessas mitigações, um episódio de 95 minutos (194 blocos) rodou do começo ao fim sem nenhuma alucinação. Exatamente um bloco falhou por erro 502 transitório da API, que o retry automático pegou na tentativa seguinte.</p>
<h3>Masterização pro YouTube<span class="hx:absolute hx:-mt-20" id="masterização-pro-youtube"></span>
    <a href="#masteriza%c3%a7%c3%a3o-pro-youtube" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O YouTube não publica número oficial, mas o consenso da indústria é que a normalização de volume dele na hora da reprodução mira em -14 LUFS. Se você entregar áudio mais alto que isso, o YouTube baixa; se entregar mais baixo, ele deixa como tá. Pra bater o alvo exato, o pipeline roda o filtro <code>loudnorm</code> do <code>ffmpeg</code> em duas passadas. A primeira mede o áudio inteiro e imprime as estatísticas (<code>input_i</code>, <code>input_tp</code>, <code>input_lra</code>, <code>input_thresh</code>) em formato JSON no stderr. A segunda passada lê essas estatísticas de volta e aplica um ganho estático linear pra pousar exato em -14 LUFS, -1.5 dBTP:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Passada 1: medir</span>
</span></span><span class="line"><span class="cl">ffmpeg -i final_en.mp3 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -af <span class="s2">&#34;highpass=f=80,loudnorm=I=-14:TP=-1.5:LRA=9:print_format=json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -f null -
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Passada 2: aplicar ganho linear com os valores medidos</span>
</span></span><span class="line"><span class="cl">ffmpeg -i final_en.mp3 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -af <span class="s2">&#34;highpass=f=80,loudnorm=I=-14:TP=-1.5:LRA=9\
</span></span></span><span class="line"><span class="cl"><span class="s2">:measured_I=-18.3:measured_TP=-3.1:measured_LRA=5.4\
</span></span></span><span class="line"><span class="cl"><span class="s2">:measured_thresh=-28.7:offset=1.2:linear=true&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ar <span class="m">48000</span> -ac <span class="m">2</span> -c:a pcm_s16le final_en_mastered.wav</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Duas passadas em vez de uma porque o modo de passada única roda em compressão dinâmica e acaba &ldquo;bombeando&rdquo; o ganho em trechos com muito silêncio. Com <code>linear=true</code> na passada 2, o ganho fica estático em cima das medições da passada 1, então não tem bombeamento. O resultado pousa dentro de ±0.1 LU do alvo, que é praticamente inaudível. O filtro <code>highpass=f=80</code> na frente derruba rumble de ar-condicionado e hum de rede elétrica abaixo de 80 Hz, que o ouvido humano não escuta mas mexe nas medições de pico.</p>
<h3>O ponto em que eu achei que estava pronto. E não estava.<span class="hx:absolute hx:-mt-20" id="o-ponto-em-que-eu-achei-que-estava-pronto-e-não-estava"></span>
    <a href="#o-ponto-em-que-eu-achei-que-estava-pronto-e-n%c3%a3o-estava" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Aqui entra a parte mais chata, e mais educativa, do projeto.</p>
<p>Eu realmente achei que tinha acertado a mão na primeira grande rodada. A pipeline fechou os 146 episódios, eu subi tudo, e parecia resolvido. A duração batia, os arquivos estavam todos lá, o processo inteiro tinha finalmente atravessado o lote completo de quase 96 horas de vídeo. Só que foi justamente depois do upload que comecei a perceber problemas que eu simplesmente não tinha antecipado.</p>
<p>O primeiro susto foi a situação dos jingles. Eu já sabia que muitos episódios do Akitando começam com aquele jingle instrumental curto antes de eu começar a falar, mas subestimei o problema. O <code>.srt</code> do YouTube praticamente não ajuda nisso, porque jingle não é fala. Às vezes ele marca como <code>[Música]</code>, às vezes não marca nada útil, às vezes a janela fica torta. Na primeira montagem, teve episódio onde o jingle sumiu, episódio onde ele entrou duplicado, e episódio onde o source-fill do áudio original já trazia o jingle certo e o meu splice vinha por cima, empurrando todo o resto pra frente. Foi o tipo de bug que só aparece quando você para pra escutar a master final de verdade, não quando olha só pra duração total.</p>
<p>A solução foi abandonar a esperança de que a legenda me diria onde estava o jingle e ir direto pra fonte. Montei um detector usando os próprios áudios originais dos vídeos e jingles de referência, com auditoria e repair pass em cima dos masters já gerados. Isso acabou virando um mini-sistema próprio: detector, auditoria, reassemble por cache, correção por janela exata. No fim dessa novela, o batch de episódios alterados caiu num conjunto de reupload bem definido e o problema dos jingles ficou finalmente sob controle. Foi um daqueles casos clássicos de produção: a primeira solução parecia suficiente até bater no acervo inteiro.</p>
<p>Só que aí veio a segunda pancada, e essa foi mais cara: eu tinha esquecido de um detalhe fundamental sobre legendas de YouTube. SRT é feita pra ser lida na tela, não pra virar roteiro de fala. Então uma ideia que no meu texto original era um parágrafo inteiro aparecia quebrada em várias cues pequenas, cada uma com sua microjanela de tempo. Na primeira versão da pipeline eu estava usando essas cues como unidade de geração na ElevenLabs. Resultado: o TTS gerava áudio em blocos coerentes localmente, mas a montagem introduzia silêncios artificiais no meio da frase. Tecnicamente alinhado, humanamente esquisito.</p>
<p>O conserto foi doloroso porque exigiu voltar várias casas. Em vez de respeitar a estrutura das cues do SRT, precisei voltar pros meus textos originais e reconstruir o chunking com base nos parágrafos que eu realmente li quando gravei os vídeos. Quase todo episódio do Akitando tem o post correspondente com a seção <code>## Script</code>, e isso salvou o projeto. Como eu fui meticuloso na época em que produzi os vídeos — escrevendo os roteiros antes, lendo exatamente aquilo na gravação e arquivando tanto os vídeos quanto os scripts — eu tinha a fonte de verdade. Então deu pra mapear os parágrafos do script aos timings do <code>.pt-BR.srt</code>, reagrupar o <code>.en.srt</code> em cima disso e regenerar os chunks no formato certo.</p>
<p>O problema é que essa mudança destrói o cache anterior. Mesmo quando o texto quase não muda, se a fronteira do chunk muda, a chamada ao TTS muda junto: muda contexto, muda prosódia, muda começo, muda fim. Então não era um retoquezinho. Era uma regeneração completa de tudo de novo. Foi aí que o custo começou a sair do meu controle inicial.</p>
<p>E quando eu achei que agora sim tinha terminado, veio a terceira paulada: os clipes externos. Muitos vídeos do canal têm trechos inseridos de outras fontes. No episódio de Ruby on Rails, por exemplo, tem 37signals, Apple, comerciais, pedaços externos no meio da narrativa. O <code>.srt</code> do YouTube não sabia lidar direito com isso. Em alguns pontos ele simplesmente alucinava texto, em outros os timings ficavam completamente tortos, em outros a fala original não tinha nada a ver com a legenda que eu estava usando de base. De novo: olhando só pra automação parecia tudo bem; escutando a master final, não estava.</p>
<p>A correção, mais uma vez, foi sair da legenda e voltar pro material original. Montei um detector de janelas de clipe externo a partir de gaps de alinhamento entre transcript, <code>.pt-BR.srt</code>, cues sem fala útil e chunks que atravessavam essas janelas. Depois, em vez de tentar dublar o que não fazia sentido dublar, o processo passou a recuperar o áudio original do vídeo exatamente nesses trechos e preencher a master com o source correto. Foi isso que consertou os casos de 37signals, Apple Education, comercial de Mac vs PC e outros inserts parecidos que eu tinha esquecido que existiam.</p>
<p>Esse foi o aprendizado mais importante dessa segunda metade do projeto: automação te leva muito longe, mas o acervo completo sempre tem mais cantos escuros do que você imagina no primeiro passe.</p>
<h2>Os números do batch de dublagem (e por que doeu mais do que eu imaginei)<span class="hx:absolute hx:-mt-20" id="os-números-do-batch-de-dublagem-e-por-que-doeu-mais-do-que-eu-imaginei"></span>
    <a href="#os-n%c3%bameros-do-batch-de-dublagem-e-por-que-doeu-mais-do-que-eu-imaginei" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu preciso me corrigir de novo. Não errei só a primeira conta. Errei também o grau de confiança que eu tinha depois da primeira rodada.</p>
<p>Quando subi pro plano Business da ElevenLabs, eu achei que finalmente estaria com folga. Na prática, não fiquei. O screenshot mais recente do dashboard mostra <strong>10.046.279 créditos consumidos de um limite de 11.000.000</strong>, sobrando <strong>953.721</strong>:</p>
<p><img src="elevenlabs-credits-dashboard.png" alt="Dashboard da ElevenLabs mostrando 10.046.279 créditos consumidos de 11.000.000"  loading="lazy" /></p>
<p>Em português claro: eu torrei mais de 91% do limite do workspace Business. E isso ajuda a entender o que &ldquo;10 milhões de créditos&rdquo; realmente significa nesse contexto. Não é só &ldquo;renderizei 146 episódios uma vez&rdquo;. É renderizar 146 episódios, descobrir um problema estrutural, voltar, corrigir, regenerar chunks, descobrir outro problema estrutural, voltar, reconstruir masters, auditar jingles, reparar janelas de clipes externos, reupar de novo. Cada rodada a mais custa. E quando a mudança invalida o cache, custa muito.</p>
<p>O custo também não para na ElevenLabs. Todo o trabalho de curadoria das legendas, reescrita de cues problemáticas, reconstrução de SRTs truncadas, auditorias de jingle, detector de clipes externos e o resto rodou via Claude Code no plano Claude Max 20×. Somando isso com a tradução em massa do blog pra inglês que eu descrevi no <a href="/2026/04/09/20-anos-de-blog-o-ano-em-que-a-ia-finalmente-me-deixou-traduzir-tudo/">artigo de aniversário</a>, o limite semanal do Claude Max 20× bateu 100%, com gasto extra acima de R$ 300. Pra quem acha que IA generativa vira grátis depois que você assinou o plano, os boletos discordam.</p>
<p>Mesmo assim, continuo achando que vale a pena. Estamos falando de 146 episódios, quase 96 horas de conteúdo técnico, mais de 5 anos de acervo do canal. Fazer isso manualmente em estúdio, com ator, direção, pickup e revisão humana em tudo, custaria uma ordem de grandeza completamente diferente.</p>
<p>O que mudou foi minha visão do projeto. No começo eu tava olhando pra isso como &ldquo;um batch grande&rdquo;. No fim, era outra coisa. Era mexer num acervo inteiro, achar exceção escondida, consertar ferrugem antiga, refazer master, rerender, reouvir.</p>
<p>O primeiro batch completo levou pouco menos de dois dias e me fez pensar que eu tinha resolvido tudo. Não tinha. A segunda rodada, com chunking por parágrafo, já foi cara. A terceira, com os reparos de jingle e os consertos de masters, mais cara ainda. E a quarta pancada veio dos clipes externos que eu não tinha modelado direito na primeira automação.</p>
<p>Mas agora, finalmente, eu acho que acabou. Ou melhor: eu espero que tenha acabado. Tudo foi enviado de novo, pela última vez. Não está perfeito no sentido absoluto da palavra. Está tão bom quanto eu consigo deixar com script, auditoria e automação, sem entrar num inferno de intervenção manual vídeo por vídeo.</p>
<p>E se isso tudo foi possível, é porque anos atrás eu tive a disciplina de produzir os vídeos do jeito certo: escrever primeiro, ler o roteiro quando gravava, e arquivar os materiais depois. Sem os vídeos originais e sem os scripts originais, eu não teria como descobrir onde o <code>.srt</code> mentia, onde o timing estava torto, onde o clipe externo entrava, onde o parágrafo original começava e terminava. Essa organização antiga foi o que permitiu consertar o projeto agora.</p>
<p>Então sim: a dublagem dos 146 episódios está pronta. Não perfeita. Mas fechada. Pelo menos, eu sinceramente espero que agora sim esteja.</p>
<h2>O primeiro teste, assista<span class="hx:absolute hx:-mt-20" id="o-primeiro-teste-assista"></span>
    <a href="#o-primeiro-teste-assista" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Fiz um vídeo teste pra provar que funciona. Dois avisos antes: primeiro, o embed abaixo já carrega com a legenda em inglês ligada por padrão, então esse lado dá pra resolver via URL. Segundo, o áudio é chato: o YouTube removeu o seletor de faixa de áudio dos players embedados lá por março desse ano. Então dentro do embed você não vai encontrar essa opção no menu de engrenagem mesmo, só roda em português. Pra ouvir a dublagem em inglês de verdade, clica em <strong><a href="https://www.youtube.com/watch?v=QNLd8TZ_JQc"target="_blank" rel="noopener">assistir direto no YouTube</a></strong>, que a página principal do YouTube ainda tem o seletor de faixa de áudio no menu de engrenagem.</p>
<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/QNLd8TZ_JQc?cc_load_policy=1&amp;cc_lang_pref=en&amp;hl=en"
    title="Akitando dublado em inglês (teste)"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>
<p>Se você tá acostumado com a dublagem automática do YouTube ou com as dublagens de IA de TikTok, compara. A diferença é gritante. A voz é a minha, com sotaque americano trabalhado, e a sincronização com o vídeo original fica dentro de 1 a 2% de desvio cumulativo, praticamente imperceptível em 95 minutos de vídeo.</p>
<h2>E o que faltava: traduzir as thumbnails<span class="hx:absolute hx:-mt-20" id="e-o-que-faltava-traduzir-as-thumbnails"></span>
    <a href="#e-o-que-faltava-traduzir-as-thumbnails" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando eu estava fechando esse post, me caiu a ficha de que tinha deixado uma ponta solta. As 146 thumbnails do meu canal estão em português, muitas com título em caixa alta grande tipo &ldquo;9 DICAS PARA PALESTRANTES&rdquo; ou &ldquo;7 RECOMENDAÇÕES DE SHOWS PARA PESSOAS DE TECH&rdquo;. De nada adianta áudio em inglês perfeito se a imagem que aparece na busca é um bloco de texto ilegível pra quem não lê português. O YouTube permite subir uma thumbnail alternativa por idioma, então eu precisava gerar versões em inglês, mantendo o resto da arte idêntico, trocando só o texto.</p>
<p>A ferramenta usa duas peças. <strong><code>yt-dlp</code></strong> é o fork moderno do antigo <code>youtube-dl</code>, uma CLI em Python que baixa qualquer coisa do YouTube (vídeos, áudios, legendas, thumbnails) sem precisar de API key. <strong>Nano Banana Pro</strong> (<code>nano-banana-pro-preview</code> na API) é o modelo de edição de imagem mais recente do Google Gemini — aceita uma imagem de entrada com um prompt e devolve a imagem editada, preservando o resto da composição quando você pede pra mexer só numa parte.</p>
<p>O pipeline tem dois passos. Passo 1: <code>yt-dlp</code> pega a thumbnail em <code>.jpg</code>, sem baixar o vídeo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yt-dlp --skip-download <span class="se">\
</span></span></span><span class="line"><span class="cl">       --write-thumbnail <span class="se">\
</span></span></span><span class="line"><span class="cl">       --convert-thumbnails jpg <span class="se">\
</span></span></span><span class="line"><span class="cl">       -o <span class="s2">&#34;thumbnails/originals/&lt;slug&gt;/&lt;video_id&gt;&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">       <span class="s2">&#34;https://www.youtube.com/watch?v=&lt;video_id&gt;&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Passo 2: mandar cada imagem pro Gemini Nano Banana Pro com um prompt que é um contrato rígido de requisitos duros, não só um &ldquo;traduz isso&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">TASK:
</span></span><span class="line"><span class="cl">Detect every piece of Portuguese text visible on this image and
</span></span><span class="line"><span class="cl">translate it to clear, natural American English. Replace the
</span></span><span class="line"><span class="cl">Portuguese text with the English translation in the same visual
</span></span><span class="line"><span class="cl">position.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">STRICT REQUIREMENTS — follow every one:
</span></span><span class="line"><span class="cl">- Preserve EVERY non-text element identically: face, pose,
</span></span><span class="line"><span class="cl">  expression, background, color palette, lighting, icons, logos,
</span></span><span class="line"><span class="cl">  decorative shapes, borders, layout. Only the text changes.
</span></span><span class="line"><span class="cl">- The English translation must be IDIOMATIC and CONFIDENT — not
</span></span><span class="line"><span class="cl">  a literal word-for-word rewrite. It&#39;s a YouTube thumbnail for a
</span></span><span class="line"><span class="cl">  tech audience, so use punchy phrasing a native English-speaking
</span></span><span class="line"><span class="cl">  tech YouTuber would write.
</span></span><span class="line"><span class="cl">- Match the original text&#39;s font family, weight, size, color,
</span></span><span class="line"><span class="cl">  stroke outline, drop shadow, and any decorative treatment.
</span></span><span class="line"><span class="cl">- If there is no Portuguese text at all on the image, return the
</span></span><span class="line"><span class="cl">  original image unchanged.</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A cláusula &ldquo;return the original image unchanged&rdquo; é o que salva os primeiros episódios do canal, que não têm texto na thumbnail, só minha cara. Sem ela, o modelo invariavelmente ia &ldquo;melhorar&rdquo; a composição, trocar a iluminação, etc.</p>
<p>Olha o resultado em dois exemplos (episódios 10 e 11):</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 16px 0;">
<div><strong>Original (PT)</strong><br><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260409_thumb_ep010_pt.jpg" alt="Thumbnail original em português: 7 Recomendações de Shows para pessoas de Tech" style="width: 100%;"></div>
<div><strong>Traduzida (EN)</strong><br><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260409_thumb_ep010_en.jpg" alt="Thumbnail traduzida em inglês: 7 TV Shows You Must Watch If You're In Tech" style="width: 100%;"></div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 16px 0;">
<div><strong>Original (PT)</strong><br><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260409_thumb_ep011_pt.jpg" alt="Thumbnail original em português: 9 Dicas para Palestrantes: venda sua caneta" style="width: 100%;"></div>
<div><strong>Traduzida (EN)</strong><br><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260409_thumb_ep011_en.jpg" alt="Thumbnail traduzida em inglês: 9 Tips for Speakers: sell your pen" style="width: 100%;"></div>
</div>
<p>Repara no detalhe do episódio 10. O modelo não traduziu &ldquo;7 Recomendações de Shows para Pessoas de Tech&rdquo; literal pra &ldquo;7 Show Recommendations for Tech People&rdquo;, que seria a versão tradutor automático. Ele reformulou pra &ldquo;7 TV Shows You Must Watch If You&rsquo;re In Tech&rdquo;, como um tech YouTuber americano de verdade escreveria. O resto da imagem fica idêntico pixel a pixel. No episódio 11, &ldquo;9 DICAS PARA PALESTRANTES: venda sua caneta&rdquo; vira &ldquo;9 TIPS FOR SPEAKERS: sell your pen&rdquo;, com fonte, caixa alta e posição preservadas. Se você não soubesse que o original era em português, não dava pra adivinhar que é uma edição automática feita por IA.</p>
<p>Custo do lado Gemini: alguns centavos por imagem, poucos dólares totais pro batch inteiro. Trivial perto dos $1.500+ do dub. Com a thumbnail resolvida, a conversão do canal pra inglês fica fechada — áudio clonado pela ElevenLabs v3 em cima das legendas curadas em <code>.srt</code>, e a imagem editada pelo Nano Banana Pro pra o título bater com o resto.</p>
<h2>A conclusão, que é o título desse post<span class="hx:absolute hx:-mt-20" id="a-conclusão-que-é-o-título-desse-post"></span>
    <a href="#a-conclus%c3%a3o-que-%c3%a9-o-t%c3%adtulo-desse-post" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando o Qwen3 TTS saiu, o hype dizia que era &ldquo;ElevenLabs killer&rdquo;. Passei semanas tentando viver com essa premissa na prática, com conteúdo real no ar toda semana. E o que eu descobri é que open source de TTS ainda tá muito longe da ElevenLabs. A diferença é grande, e ela aparece justamente quando você sai do tweet de demo de 5 segundos e coloca o modelo pra rodar um podcast semanal de 30 minutos de verdade.</p>
<p>Na prática, o Qwen3 nem bate o modelo v2 da ElevenLabs. O v3, que é o atual, fica um degrau acima do v2 ainda. A prosódia sai melhor, as tags de emoção no meio do texto funcionam em português e em inglês sem esforço, e a API fica de pé sem você precisar manter servidor nenhum. O custo por caractere é um pouco mais alto, mas pro meu volume atual ele fica confortavelmente dentro do orçamento e me devolve horas que eu gastava cuidando de GPU.</p>
<p>A lição aqui é a mesma que a galera de LLMs tá aprendendo devagar. Demo de tweet de 30 segundos é uma coisa, produção é outra coisa completamente diferente. Open source tem seu nicho, principalmente quando você tem dado sensível que não pode sair de casa, ou quando você tem muita GPU ociosa e pouco orçamento recorrente. Só que pra uso sério de TTS em produto comercial, aqui em abril de 2026, a ElevenLabs segue imbatível. O Qwen3 não matou ninguém.</p>
<p>E não esquece: assina o <a href="https://open.spotify.com/show/7MzG2UB7IAkC3GAwEXEIVD"target="_blank" rel="noopener">The M.Akita Chronicles no Spotify</a> pra não perder episódio novo desses, feito com a nova pipeline.</p>
]]></content:encoded><category>midia-generativa</category><category>modelos-locais</category><category>reviews</category></item><item><title>Servindo IA na Nuvem: Meu TTS pessoal | Bastidores do The M.Akita Chronicles</title><link>https://akitaonrails.github.io/2026/02/18/servindo-ia-na-nuvem-meu-tts-pessoal-bastidores-do-the-m-akita-chronicles/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/02/18/servindo-ia-na-nuvem-meu-tts-pessoal-bastidores-do-the-m-akita-chronicles/</guid><pubDate>Wed, 18 Feb 2026 15:28:46 +0000</pubDate><description>&lt;p&gt;Este post vai fazer parte de uma série; acompanhe pela tag &lt;a href="https://akitaonrails.github.io/tags/the-m-akita-chronicles/"&gt;/themakitachronicles&lt;/a&gt;. Esta é a parte 3.&lt;/p&gt;
&lt;p&gt;E não deixe de assinar minha nova newsletter &lt;a href="https://themakitachronicles.com/"target="_blank" rel="noopener"&gt;The M.Akita Chronicles&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Já vou falar do podcast hoje; lembrem-se de que o &lt;a href="https://blog.themakitachronicles.com/podcast-transcripts/"target="_blank" rel="noopener"&gt;episódio piloto já está no ar&lt;/a&gt; (transcripts e RSS pra assinar) e no canal no Spotify.&lt;/p&gt;
&lt;p&gt;&amp;ndash;&lt;/p&gt;
&lt;p&gt;Todo tutorial de IA começa com &lt;em&gt;&amp;ldquo;instale o modelo, rode inference, pronto!&amp;rdquo;&lt;/em&gt;. Nenhum te conta o que acontece quando você precisa rodar isso em produção com uptime real, custo controlado, e qualidade consistente.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este post vai fazer parte de uma série; acompanhe pela tag <a href="/tags/the-m-akita-chronicles/">/themakitachronicles</a>. Esta é a parte 3.</p>
<p>E não deixe de assinar minha nova newsletter <a href="https://themakitachronicles.com/"target="_blank" rel="noopener">The M.Akita Chronicles</a>!</p>
<p>Já vou falar do podcast hoje; lembrem-se de que o <a href="https://blog.themakitachronicles.com/podcast-transcripts/"target="_blank" rel="noopener">episódio piloto já está no ar</a> (transcripts e RSS pra assinar) e no canal no Spotify.</p>
<p>&ndash;</p>
<p>Todo tutorial de IA começa com <em>&ldquo;instale o modelo, rode inference, pronto!&rdquo;</em>. Nenhum te conta o que acontece quando você precisa rodar isso em produção com uptime real, custo controlado, e qualidade consistente.</p>
<p>Vou contar como foi colocar um modelo de TTS (Text-to-Speech) em produção numa GPU na nuvem. Porque se você está pensando em servir modelos de IA, precisa saber no que está se metendo.</p>
<h2>O Cenário<span class="hx:absolute hx:-mt-20" id="o-cenário"></span>
    <a href="#o-cen%c3%a1rio" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><blockquote>
  <p>O objetivo: gerar um podcast de ~30 minutos a partir de texto, com duas vozes distintas, qualidade de broadcast, pronto pra Spotify. Toda semana, automaticamente, sem intervenção humana.</p>

</blockquote>
<p>O modelo escolhido: <a href="https://qwen.ai/blog?id=qwen3tts-0115"target="_blank" rel="noopener"><strong>Qwen3-TTS</strong></a>, um modelo de text-to-speech que suporta voice cloning e fine-tuning. Roda em GPU — precisa de pelo menos 6GB de VRAM pra inference.</p>
<p>A plataforma: <a href="https://runpod.io/"target="_blank" rel="noopener"><strong>RunPod</strong></a>, que oferece GPUs serverless com cobrança por segundo de uso.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-18_12-33-45.jpg" alt="runpod"  loading="lazy" /></p>
<p>Parece simples, né? Pois aguarde.</p>
<h2>Problema 1: Cold Start — O Assassino Silencioso<span class="hx:absolute hx:-mt-20" id="problema-1-cold-start--o-assassino-silencioso"></span>
    <a href="#problema-1-cold-start--o-assassino-silencioso" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>GPU serverless funciona assim: quando não tem requisição, o worker desliga. Quando chega uma requisição, ele liga, carrega o modelo na VRAM, e processa.</p>
<p>O tempo de &ldquo;carregar o modelo na VRAM&rdquo; é o <em>cold start</em>. Pra um modelo de ~4GB, estamos falando de <strong>30 a 90 segundos</strong>. Sua requisição HTTP fica pendurada, esperando. Se seu timeout é de 30 segundos, você nem chega a ver o modelo funcionar — só vê timeout.</p>
<p>A solução ingênua: <em>&ldquo;aumenta o timeout!&rdquo;</em>. A solução real: <strong>health check com polling</strong>.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@app.get</span><span class="p">(</span><span class="s2">&#34;/health&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">health</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">model_loaded</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">JSONResponse</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">status_code</span><span class="o">=</span><span class="mi">503</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">content</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;loading&#34;</span><span class="p">,</span> <span class="s2">&#34;detail&#34;</span><span class="p">:</span> <span class="s2">&#34;Model loading...&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;ready&#34;</span><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O cliente (no caso, um job Rails) faz polling no <code>/health</code> antes de mandar a requisição real:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">wait_for_server</span><span class="p">(</span><span class="ss">max_wait</span><span class="p">:</span> <span class="mi">180</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">deadline</span> <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">current</span> <span class="o">+</span> <span class="n">max_wait</span>
</span></span><span class="line"><span class="cl">  <span class="kp">loop</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">http_get</span><span class="p">(</span><span class="s2">&#34;/health&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">code</span> <span class="o">==</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">    <span class="k">raise</span> <span class="s2">&#34;Server não iniciou a tempo&#34;</span> <span class="k">if</span> <span class="no">Time</span><span class="o">.</span><span class="n">current</span> <span class="o">&gt;</span> <span class="n">deadline</span>
</span></span><span class="line"><span class="cl">    <span class="nb">sleep</span> <span class="mi">5</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Parece primitivo? É. Mas funciona melhor do que qualquer solução sofisticada de &ldquo;warm pool&rdquo; que custa 10x mais.</p>
<h2>Problema 2: Onde Ficam os Pesos do Modelo?<span class="hx:absolute hx:-mt-20" id="problema-2-onde-ficam-os-pesos-do-modelo"></span>
    <a href="#problema-2-onde-ficam-os-pesos-do-modelo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um modelo de 4GB precisa estar disponível quando o worker inicia. Três opções:</p>
<ol>
<li><strong>Bake no Docker</strong>: Imagem de 4GB+. Build lento. Push lento. Pull lento.</li>
<li><strong>Download do HuggingFace no boot</strong>: Depende de rede. Pode falhar. Adiciona minutos ao cold start.</li>
<li><strong>Network Volume</strong>: Disco persistente montado no worker.</li>
</ol>
<p>A opção 3 é a correta. O RunPod oferece Network Volumes que persistem entre execuções. O modelo fica lá, sempre pronto. O cold start é só o tempo de carregar da disk pra VRAM, sem download.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-18_12-35-43.jpg" alt="network storage"  loading="lazy" /></p>
<p>Mas tem uma pegadinha: o volume pode estar montado ou não, dependendo de como o worker é configurado. Então o código de setup precisa ser resiliente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">NETWORK_VOLUME</span> <span class="o">=</span> <span class="s2">&#34;/runpod-volume&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">LOCAL_FALLBACK</span> <span class="o">=</span> <span class="s2">&#34;/workspace/models&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">resolve_model_path</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isdir</span><span class="p">(</span><span class="n">NETWORK_VOLUME</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">base</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">NETWORK_VOLUME</span><span class="p">,</span> <span class="s2">&#34;huggingface&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;HF_HOME&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">base</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="s2">&#34;hub&#34;</span><span class="p">,</span> <span class="s2">&#34;models--Qwen--Qwen3-TTS&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Fallback: download local</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">LOCAL_FALLBACK</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Auto-detecção. Se o volume existe, usa. Se não, funciona do mesmo jeito (mais lento no primeiro boot). <strong>Nunca assuma que a infraestrutura está perfeita.</strong></p>
<h2>Problema 3: Dois Modos de Voz, Dois Conjuntos de Parâmetros<span class="hx:absolute hx:-mt-20" id="problema-3-dois-modos-de-voz-dois-conjuntos-de-parâmetros"></span>
    <a href="#problema-3-dois-modos-de-voz-dois-conjuntos-de-par%c3%a2metros" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui está algo que nenhuma documentação vai te dizer: modelos de TTS com voice cloning e fine-tuning precisam de <strong>parâmetros de sampling completamente diferentes</strong> dependendo do modo.</p>
<p>Com uma voz fine-tuned (treinada especificamente no modelo), você quer parâmetros conservadores:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>temperature: 0.5
top_p: 0.8
repetition_penalty: 1.1</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com voice cloning (voz base + áudio de referência), os mesmos parâmetros produzem <strong>ruído branco</strong>. Literalmente. White noise. Porque o modelo de clone precisa de mais &ldquo;criatividade&rdquo; pra reconstruir as características vocais:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>temperature: 0.9
top_p: 0.95
repetition_penalty: 1.05</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Descobri isso depois de horas gerando áudio inutilizável. A documentação do modelo não menciona. Os papers acadêmicos não mencionam. É o tipo de conhecimento que só vem de <strong>experimentação em produção</strong>.</p>
<h2>Problema 4: O Áudio de Referência Importa Mais que o Modelo<span class="hx:absolute hx:-mt-20" id="problema-4-o-áudio-de-referência-importa-mais-que-o-modelo"></span>
    <a href="#problema-4-o-%c3%a1udio-de-refer%c3%aancia-importa-mais-que-o-modelo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Voice cloning usa um trecho de áudio como referência pra replicar a voz. Parece simples: grava 10 segundos, passa pro modelo e pronto.</p>
<p>Exceto que se o áudio de referência começa com a fala imediatamente — sem nem 200 milissegundos de silêncio no início — o modelo aprende que &ldquo;o áudio começa sem onset&rdquo;. E <strong>toda geração subsequente</strong> vai cortar a primeira sílaba.</p>
<p>Isso não é um bug do modelo. É o modelo fazendo exatamente o que você pediu: <em>&ldquo;gere áudio que começa igual a essa referência&rdquo;</em>. Se a referência começa cortada, a geração começa cortada.</p>
<p>A correção não é pós-processamento (o áudio já foi gerado sem a sílaba). É <strong>corrigir a referência</strong>: garantir pelo menos 300ms de silêncio antes da fala.</p>
<p>Pra quem vem do mundo de software, isso é contra-intuitivo. Estamos acostumados a tratar dados de entrada como imutáveis e consertar na saída. Com modelos generativos, a qualidade da entrada é <strong>tudo</strong>.</p>
<h2>Problema 5: Timeout e Limites de Geração<span class="hx:absolute hx:-mt-20" id="problema-5-timeout-e-limites-de-geração"></span>
    <a href="#problema-5-timeout-e-limites-de-gera%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um modelo de TTS gera áudio token por token. Se você não limitar, ele pode gerar indefinidamente — e no RunPod, você paga por segundo de GPU. Um turno de diálogo de 30 palavras não deveria gerar 3 minutos de áudio, mas sem limite, pode.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">MAX_NEW_TOKENS</span> <span class="o">=</span> <span class="mi">720</span>  <span class="c1"># ~60 segundos de áudio a 12Hz</span>
</span></span><span class="line"><span class="cl"><span class="n">GENERATION_TIMEOUT</span> <span class="o">=</span> <span class="mi">180</span>  <span class="c1"># 3 minutos absolutos</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">ThreadPoolExecutor</span><span class="p">(</span><span class="n">max_workers</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span> <span class="k">as</span> <span class="n">executor</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">future</span> <span class="o">=</span> <span class="n">executor</span><span class="o">.</span><span class="n">submit</span><span class="p">(</span><span class="n">model</span><span class="o">.</span><span class="n">generate</span><span class="p">,</span> <span class="o">**</span><span class="n">params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="n">GENERATION_TIMEOUT</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Dupla proteção: limite de tokens na geração e timeout absoluto no processo. Se qualquer um disparar, o turno falha e pode ser re-tentado com parâmetros ajustados.</p>
<h2>Problema 6: Pós-Processamento é Metade do Trabalho<span class="hx:absolute hx:-mt-20" id="problema-6-pós-processamento-é-metade-do-trabalho"></span>
    <a href="#problema-6-p%c3%b3s-processamento-%c3%a9-metade-do-trabalho" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O modelo gera WAV cru. Pra chegar em áudio de podcast quality, o caminho é longo:</p>
<ol>
<li><strong>Normalização de loudness</strong>: Spotify exige -14 LUFS. Sem normalização, cada turno tem volume diferente.</li>
<li><strong>Re-encoding</strong>: Os WAVs do modelo podem ter formatos sutilmente diferentes (sample rate, bit depth). Concatenar com <code>-c copy</code> causa <strong>segmentos silenciosos</strong>. Precisa re-encodar tudo pro mesmo formato.</li>
<li><strong>Conversão pra MP3</strong>: CBR 192kbps, 44.1kHz, com metadata ID3.</li>
</ol>
<p>E a normalização de loudness precisa ser <strong>two-pass</strong>: primeiro mede, depois aplica. Single-pass é chute — e chute em produção gera áudio com volume inconsistente entre episódios.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Pass 1: análise</span>
</span></span><span class="line"><span class="cl">ffmpeg -i input.wav -af <span class="nv">loudnorm</span><span class="o">=</span><span class="nv">I</span><span class="o">=</span>-14:print_format<span class="o">=</span>json -f null -
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Pass 2: aplicação dos valores medidos</span>
</span></span><span class="line"><span class="cl">ffmpeg -i input.wav -af <span class="nv">loudnorm</span><span class="o">=</span><span class="nv">I</span><span class="o">=</span>-14:measured_I<span class="o">=</span>-18.5:measured_TP<span class="o">=</span>-2.1:... output.wav</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tudo isso roda no servidor, depois da geração do TTS. E precisa de <code>ffmpeg</code> instalado no container — mais uma dependência pra gerenciar.</p>
<h2>Problema 7: O Script Importa Mais que o Modelo de Voz<span class="hx:absolute hx:-mt-20" id="problema-7-o-script-importa-mais-que-o-modelo-de-voz"></span>
    <a href="#problema-7-o-script-importa-mais-que-o-modelo-de-voz" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois de resolver GPU, cold start, voice cloning, sampling, normalização — o podcast sai. E soa&hellip; robótico. Não por causa do TTS. O TTS é excelente. Soa robótico porque o <strong>texto</strong> é robótico.</p>
<p>Esse é o problema que ninguém menciona em pipelines de TTS: a qualidade do áudio começa no script, não no modelo de voz. Um TTS perfeito lendo um texto seco e formal produz áudio seco e formal. Garbage in, garbage out — mas num nível que você não espera.</p>
<h3>Duas Passadas: Draft e Refine<span class="hx:absolute hx:-mt-20" id="duas-passadas-draft-e-refine"></span>
    <a href="#duas-passadas-draft-e-refine" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A solução é gerar o script em duas passadas de LLM:</p>
<p><strong>Passada 1 (Draft)</strong>: Transforma a newsletter em diálogo. Aqui o LLM recebe o conteúdo completo e cria a estrutura — quem fala o quê, em que ordem, com quais transições. <strong>Sem mudar o conteúdo, só a forma!</strong></p>
<p><strong>Passada 2 (Refine)</strong>: Pega o draft e torna natural. Adiciona hesitações, reações, interrupções, auto-correções. Transforma &ldquo;script lido&rdquo; em &ldquo;conversa real&rdquo;.</p>
<p>Por que não fazer tudo numa passada? Porque são tarefas cognitivamente diferentes. Estruturar conteúdo e adicionar naturalidade ao mesmo tempo resulta num texto que não faz nem um nem outro direito. Duas passadas especializadas produzem resultado consistentemente melhor.</p>
<h3>Personalidade Precisa de Instruções Brutalmente Específicas<span class="hx:absolute hx:-mt-20" id="personalidade-precisa-de-instruções-brutalmente-específicas"></span>
    <a href="#personalidade-precisa-de-instru%c3%a7%c3%b5es-brutalmente-espec%c3%adficas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>&ldquo;Faça o Akita soar confiante&rdquo; não funciona. O LLM vai produzir confiança genérica — frases motivacionais, tom positivo. Não é isso. Akita é confiante porque é <strong>assertivo e impaciente</strong>. A diferença está nos detalhes do prompt:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gu">## Akita Delivery Style
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Declarative, not tentative**: &#34;Isso é óbvio&#34; not &#34;Eu acho que talvez isso seja...&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Dismissive of nonsense**: &#34;Isso é bobagem&#34;, &#34;Não tem nenhum mistério aqui&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Owns his track record**: &#34;Eu avisei&#34;, &#34;Quem assistiu meu canal já sabe&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Never hedges**: No &#34;talvez&#34;, &#34;quem sabe&#34;, &#34;pode ser que&#34;
</span></span><span class="line"><span class="cl">- <span class="gs">**Impatient with the obvious**</span>: &#34;Vocês já sabem como funciona&#34;</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E pro Marvin (o co-host robô):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="k">-</span> **Filler words** (Marvin only): &#34;bom...&#34;, &#34;olha...&#34;, &#34;enfim&#34;, &#34;pois é&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Self-corrections** (Marvin only): &#34;quer dizer...&#34;, &#34;ou melhor...&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> Akita doesn&#39;t second-guess himself — if he corrects, it&#39;s
</span></span><span class="line"><span class="cl">  &#34;Não, peraí&#34; and he restates with even MORE conviction</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Sem esse nível de especificidade, os dois personagens soam iguais. O LLM precisa de exemplos concretos de <strong>como</strong> cada um fala, não de descrições abstratas de personalidade.</p>
<h3>Normalização Pra Fala: O Que Parece Certo no Texto Soa Errado no Áudio<span class="hx:absolute hx:-mt-20" id="normalização-pra-fala-o-que-parece-certo-no-texto-soa-errado-no-áudio"></span>
    <a href="#normaliza%c3%a7%c3%a3o-pra-fala-o-que-parece-certo-no-texto-soa-errado-no-%c3%a1udio" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Siglas. O inimigo invisível do TTS. &ldquo;EUA&rdquo; no texto é perfeitamente legível. Falado pelo TTS, sai &ldquo;éu-á&rdquo; — incompreensível. O prompt precisa de regras explícitas de normalização:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gu">## Spoken Text Normalization (MANDATORY)
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> <span class="sb">`EUA`</span> → <span class="sb">`Estados Unidos`</span>
</span></span><span class="line"><span class="cl"><span class="k">-</span> <span class="sb">`UE`</span> → <span class="sb">`União Europeia`</span>
</span></span><span class="line"><span class="cl"><span class="k">-</span> <span class="sb">`IA`</span> → <span class="sb">`inteligência artificial`</span>
</span></span><span class="line"><span class="cl"><span class="k">-</span> <span class="sb">`LLM`</span> → <span class="sb">`modelo de linguagem`</span>
</span></span><span class="line"><span class="cl">- <span class="sb">`API`</span> → <span class="sb">`interface de programação`</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso precisa estar no prompt de <strong>ambas</strong> as passadas (draft e refine), porque o LLM tem tendência a reintroduzir siglas que removeu na passada anterior.</p>
<h3>O LLM Vai Reescrever Suas Opiniões (Se Você Deixar)<span class="hx:absolute hx:-mt-20" id="o-llm-vai-reescrever-suas-opiniões-se-você-deixar"></span>
    <a href="#o-llm-vai-reescrever-suas-opini%c3%b5es-se-voc%c3%aa-deixar" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Essa foi a lição mais sutil. A newsletter tem comentários opinativos do Akita e do Marvin em cada seção. Quando o LLM transforma em diálogo, ele <strong>substitui</strong> as opiniões por versões mais &ldquo;balanceadas&rdquo; e &ldquo;nuançadas&rdquo;. Akita que dizia &ldquo;isso é bobagem&rdquo; vira Akita dizendo &ldquo;isso tem seus méritos, mas&hellip;&rdquo;. Marvin pessimista vira Marvin &ldquo;levemente cético&rdquo;.</p>
<p>O LLM faz isso porque foi treinado pra ser helpful e balanced. Opiniões fortes parecem &ldquo;unhelpful&rdquo; pro modelo, então ele suaviza. A instrução original — &ldquo;use os comentários da newsletter como sementes pra conversa&rdquo; — era vaga demais. Dava liberdade pro modelo reinterpretar.</p>
<p>A correção é ser explícito e inflexível:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">**PRESERVE THE ORIGINAL OPINIONS**: The newsletter contains Akita and
</span></span><span class="line"><span class="cl">Marvin commentary on each topic. These opinions are the author&#39;s real
</span></span><span class="line"><span class="cl">voice — you MUST preserve their substance, stance, and conclusions.
</span></span><span class="line"><span class="cl">Make the delivery conversational, but do NOT change what they actually
</span></span><span class="line"><span class="cl">think. If Akita&#39;s comment says &#34;isso é bobagem&#34;, the podcast version
</span></span><span class="line"><span class="cl">must convey the same dismissal. Rephrase for spoken delivery, but
</span></span><span class="line"><span class="cl">never substitute a different opinion.</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É a diferença entre &ldquo;adapte pra conversa&rdquo; (LLM entende: &ldquo;reescreva como quiser&rdquo;) e &ldquo;mude o COMO, nunca o QUÊ&rdquo; (LLM entende: &ldquo;mantenha a opinião, troque a embalagem&rdquo;).</p>
<p>E isso vale pra qualquer prompt engineering com conteúdo autoral: <strong>o LLM vai homogeneizar sua voz se você não for explícito sobre preservá-la</strong>. E num podcast onde a personalidade dos hosts É o produto, perder a voz é perder tudo.</p>
<h2>O Custo Real de IA em Produção<span class="hx:absolute hx:-mt-20" id="o-custo-real-de-ia-em-produção"></span>
    <a href="#o-custo-real-de-ia-em-produ%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Vamos ser práticos sobre custos no RunPod:</p>
<table>
  <thead>
      <tr>
          <th>Recurso</th>
          <th>Custo</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GPU L40 (serverless)</td>
          <td>~$0.39/hora</td>
      </tr>
      <tr>
          <td>Network Volume 20GB</td>
          <td>$1.40/mês</td>
      </tr>
      <tr>
          <td>Geração de 1 episódio (~30 min áudio)</td>
          <td>~$0.50–1.50</td>
      </tr>
  </tbody>
</table>
<p>O custo por episódio é baixo. O custo de <strong>descobrir como fazer funcionar</strong> é alto. Foram dezenas de horas de experimentação, logs incompreensíveis de CUDA, gerações falhadas, e áudio com qualidade inutilizável antes de chegar numa pipeline estável.</p>
<p>E o custo cognitivo: manter dois ecossistemas (Python pra ML, Ruby pra aplicação) sincronizados, com interfaces HTTP entre eles, e debugging que cruza fronteiras de linguagem.</p>
<p>De qualquer forma, pra quem pensou <em>&ldquo;Por que não Elevenlabs?&rdquo;</em>, bom, eu consegui qualidade similar, numa fração do custo, e aprendi muito no processo (e vocês também, lendo aqui).</p>
<h2>Lições<span class="hx:absolute hx:-mt-20" id="lições"></span>
    <a href="#li%c3%a7%c3%b5es" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ol>
<li>
<p><strong>Cold start é um requisito, não um bug.</strong> Design seu sistema esperando que o modelo demore pra carregar.</p>
</li>
<li>
<p><strong>Network volumes são obrigatórios</strong> pra modelos grandes. Não bake pesos no Docker, não faça download no boot.</p>
</li>
<li>
<p><strong>Parâmetros de sampling não são universais.</strong> Cada modo de operação do mesmo modelo pode precisar de configuração completamente diferente.</p>
</li>
<li>
<p><strong>A qualidade da entrada domina a qualidade da saída.</strong> Tanto pro áudio de referência do TTS quanto pro script. Se a referência corta a primeira sílaba, toda geração corta. Se o script é genérico, o áudio é genérico.</p>
</li>
<li>
<p><strong>Timeouts em todas as camadas.</strong> Token limit, process timeout, HTTP timeout. Se não limitar, o custo escala e a geração trava.</p>
</li>
<li>
<p><strong>Pós-processamento é metade do pipeline.</strong> O modelo gera matéria-prima. Normalização, encoding, metadata — tudo isso é trabalho real.</p>
</li>
<li>
<p><strong>Separe o servidor de ML da aplicação.</strong> Python pra inference, Ruby pra orquestração. Tentar fazer tudo numa linguagem é receita pra frustração.</p>
</li>
<li>
<p><strong>Prompts precisam de duas passadas pra conteúdo complexo.</strong> Estruturar e naturalizar são tarefas diferentes. Forçar as duas numa passada produz resultado medíocre em ambas.</p>
</li>
<li>
<p><strong>O LLM vai homogeneizar sua voz.</strong> Opiniões fortes viram &ldquo;nuanced takes&rdquo; se você não for explícito sobre preservá-las. Instrua o modelo a mudar a embalagem, nunca o conteúdo.</p>
</li>
</ol>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>IA em produção não é um tutorial de HuggingFace com 10 linhas. É infraestrutura, é experimentação, é debugging de problemas que não existem em software tradicional. Mas quando funciona — quando seu sistema gera um podcast inteiro automaticamente toda semana, com qualidade broadcast, sem intervenção humana — a sensação é que valeu cada hora de dor de cabeça.</p>
<p>O truque é não subestimar a complexidade. E ter um sistema robusto (como os jobs do Rails que mencionei no primeiro post) orquestrando tudo — porque IA sem orquestração confiável é só um script caro que trava.</p>
]]></content:encoded><category>the-m-akita-chronicles</category><category>midia-generativa</category><category>cloud</category></item><item><title>AI 3D: Já dá pra modelar 3D com prompts?</title><link>https://akitaonrails.github.io/2026/01/23/ai-3d-ja-da-pra-modelar-3d-com-prompts/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/01/23/ai-3d-ja-da-pra-modelar-3d-com-prompts/</guid><pubDate>Fri, 23 Jan 2026 12:13:58 -0300</pubDate><description>&lt;p&gt;Alguns dias atrás eu tinha feito um post sobre como usar Blender com IAs locais usando Blender MCP. E a conclusão era que a qualidade era ruim.&lt;/p&gt;
&lt;p&gt;Mas fui prematuro na conclusão. Opções open source ainda variam bastante em qualidade, maioria não é muito boa, mas as opções comerciais são muito boas e bem estáveis. Não vou listar tudo que existe então fica minha recomendação de dois canais no YouTube pra se manter atualizado:&lt;/p&gt;</description><content:encoded><![CDATA[<p>Alguns dias atrás eu tinha feito um post sobre como usar Blender com IAs locais usando Blender MCP. E a conclusão era que a qualidade era ruim.</p>
<p>Mas fui prematuro na conclusão. Opções open source ainda variam bastante em qualidade, maioria não é muito boa, mas as opções comerciais são muito boas e bem estáveis. Não vou listar tudo que existe então fica minha recomendação de dois canais no YouTube pra se manter atualizado:</p>
<ul>
<li><a href="https://www.youtube.com/@PixelArtistry_"target="_blank" rel="noopener"><strong>PixelArtistry_</strong></a></li>
<li><a href="https://www.youtube.com/channel/UCRW08KcTVjXEmBzBsVl7XjA"target="_blank" rel="noopener"><strong>Stefan 3D AI Lab</strong></a></li>
</ul>
<p>Na prática, existem vários workflows de ComfyUI pra brincar, como neste tutorial ensinando a usar o <a href="https://www.youtube.com/watch?v=H7gqMnK7wUc"target="_blank" rel="noopener">recém-lançado modelo Trellis2</a>, da Microsoft. Não funciona perfeito como no video todas as vezes. Depende muito da imagem que se passar. Nos meus testes só consegui uns &ldquo;blobs&rdquo; deformados.</p>
<p>Mas no mundo comercial, existem 3 ferramentas que vale criar conta:</p>
<ul>
<li><a href="https://3d.hunyuanglobal.com/"target="_blank" rel="noopener">Hunyuan Global</a>, da Tencent. Existe a plataforma em chinês - que é mais completa - e a versão global em inglês que é mais limitada. Mas com ele é possível criar modelos 3D a partir de imagens com qualidade excepcional.</li>
<li><a href="https://www.hitem3d.ai/home"target="_blank" rel="noopener">Hitem3D</a> - é concorrente da Hunyuan, também é excelente pra gerar modelos 3D a partir de imagens. Vale testar nas duas porque dependendendo das imagens que se passar, uma pode resultar melhor que a outra.</li>
<li><a href="https://yvo3d.com/"target="_blank" rel="noopener">Yvo3D</a> - é uma das mais caras, mas especificamente pra gerar texturas em alta qualidade, parece que é o melhor disparado.</li>
</ul>
<p>Tem várias outras plataformas e pra se manter atualizado existe o site <a href="https://www.top3d.ai/"target="_blank" rel="noopener"><strong>Top3D.ai</strong></a> com o ranking das melhores ferramentas. A maioria costuma ter planos gratuitos, trial, ou coisas assim, então é bom testar quantas puder. O preço não é &ldquo;caro&rdquo; se você é um profissional que vai usar em trabalho pra cliente. Só é caro pra caras com eu, que usam como hobby.</p>
<p>Algumas que vale a pena testar são Hyper3D Rodin, Tripo AI, Prism e SAM 3D. Nos canais de YouTube que recomendei, tem videos sobre eles, então pelo menos assista.</p>
<h3>A carta na manga: Nano Banana<span class="hx:absolute hx:-mt-20" id="a-carta-na-manga-nano-banana"></span>
    <a href="#a-carta-na-manga-nano-banana" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O grande truque pra qualquer plataforma, seja open source ou comercial, é começar com imagens de boa qualidade. Imagens de Google Images, ou suas fotos mal tiradas, sempre vai resultar em modelos 3D ruins.</p>
<p>Pra limpar imagens, o jeito mais fácil é ir no <a href="https://gemini.google/br/overview/image-generation/?hl=pt-BR"target="_blank" rel="noopener"><strong>Google Nano Banana</strong></a>. É de longe a melhor plataforma de IA pra editar e gerar imagens.</p>
<p>Como exemplo, resolvi partir de uma imagem que minha namorada encomendou pra um artista no <strong>Fiverr</strong> e que mandamos fazer adesivos pra colar no nosso arcade. Esta imagem:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123123341_TOMIKO_NAGAI%20left.png" alt="arcade original left"  loading="lazy" /></p>
<p>A primeira coisa que pedi ao Nano Banana foi pra remover o background e separar só a personagem. É o tipo de coisa que antigamente precisaria de Photoshop pra fazer. Olha como fica agora:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123123457_no-background.png" alt="arcade no background"  loading="lazy" /></p>
<p>(não liguem que tá espelhado, fiquei com preguiça de pegar a imagem certa.)</p>
<p>Enfim, ainda não é o suficiente pra gerar 3D. No caso específico de gerar modelos de personagens, o ideal é ter imagens em <strong>&ldquo;T-Pose&rdquo;</strong>, &ldquo;pose em T&rdquo;, que é a pessoa ereta de pé com os braços abertos como Jesus. E ter fotos exatamente de frente, de trás, pela esquerda e pela direita.</p>
<p>E dá pra pedir pro Nano Banana fazer exatamente isso: <em>&ldquo;generate a front photo of this character in T-Pose, for 3D modeling&rdquo;</em> e ele vai gerar isso:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123124015_front.png" alt="t-pose front"  loading="lazy" /></p>
<p>Metade do trabalho acabou neste passo. Sem isso não dá pra gerar modelos bons. Agora é só mandar gerar os outros ângulos, como de costas:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123124004_back.png" alt="t-pose back"  loading="lazy" /></p>
<p>Mesma coisa, imagens do ângulo na esquerda, na direita e pronto. Pelo menos 4 imagens <strong>consistentes, coerentes, limpas</strong>.</p>
<h2>Hunyuan Global<span class="hx:absolute hx:-mt-20" id="hunyuan-global"></span>
    <a href="#hunyuan-global" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora é a parte simples. Basta escolher Hunyuan ou Hitem3D. No caso, escolhi Hunyuan:</p>
<p><a href="https://3d.hunyuanglobal.com/"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123124227_screenshot-2026-01-23_12-42-16.png" alt="Hunyuan Generate"  loading="lazy" /></a></p>
<p>Só escolher o novo modelo versão 3.0 e mandar gerar com 1.5 milhões de polígonos (sim, é bem pesado, mas depois é só fazer remesh) e opcionalmente mandar gerar texturas (já sabendo que Hunyuan não é bom com texturas).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123124359_screenshot-2026-01-18_13-35-13.png" alt="Hunyuan model"  loading="lazy" /></p>
<p>E olha o resultado. Perfeito! Do jeito que tá já dá pra usar. Como falei, ele não é muito bom com texturas, mas como meu objetivo pra esse teste foi imprimir na minha impressora 3D, não me preocupei com texturas.</p>
<p>Abrindo no Blender, posso colocar um modifier de remesh pra diminuir a resolução de polígonos e converter tudo em quads e fica assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123124605_screenshot-2026-01-18_13-46-35.png" alt="Blender model"  loading="lazy" /></p>
<p>E de cara já tentei imprimir e saiu assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123124749_20260123_124442.png" alt="Bambulab 3d Print"  loading="lazy" /></p>
<p>Fiquei particularmente impressionado com os detalhes: nada torto, nada fora do lugar, mãos com 5 dedos, proporções corretas, detalhes no rosto perfeitos, cabelo perfeito.</p>
<blockquote>
  <p>Pense nisso: fui do nada, ao IA, a uma impressão 3D física!</p>

</blockquote>
<p>E agora que eu tenho esse modelo, posso mandar pro <a href="https://www.mixamo.com/#/"target="_blank" rel="noopener"><strong>Adobe Mixamo</strong></a>, que é uma ferramenta online pra re-usar poses pré-prontas.</p>
<p><a href="https://www.mixamo.com/#/"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123124934_screenshot-2026-01-18_14-12-07.png" alt="mixamo pose"  loading="lazy" /></a></p>
<p>Consigo subir meu modelo, calibrar a armatura/esqueleto e colocar na pose que eu quiser. Eis um exemplo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123125208_screenshot-2026-01-23_12-51-46.png" alt="model sitting"  loading="lazy" /></p>
<p>No Blender consigo calibrar peso de partes do mesh e outros detalhes pra melhorar a pose, mas os 80% do trabalho já foi feito automaticamente. Com isso consigo fazer animações e muito mais.</p>
<p>Portanto, sim, modelagem 3D já é uma realidade com IA. Existem dezenas de ferramentas pra diferentes casos de uso e especialidades, como geração de texturas, esqueletos, animação.</p>
<p>O principal: comece com uma boa fundação usando Nano Banana como falei, esse é o principal truque pro resto funciona direito. E fiquem sempre de olho na Tencent: o Hunyuan é o modelo mais interessante.</p>
<p>No mundo open source tem o Hunyuan pequeno pra rodar local e agora o Trellis2. Também fiquem de olho em novos workflow que usem esses dois juntos no ComfyUI.</p>
<p>Do meu lado, pra fazer coisas simples que eu quero imprimir em 3D na minha Bambulab X1C, isso já tá mais que excelente!</p>
]]></content:encoded><category>midia-generativa</category><category>hardware</category></item><item><title>Mudando roupas usando I.A. (ComfyUI)</title><link>https://akitaonrails.github.io/2025/04/23/mudando-roupas-usando-i-a-comfyui/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/23/mudando-roupas-usando-i-a-comfyui/</guid><pubDate>Wed, 23 Apr 2025 17:45:00 -0300</pubDate><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Atualização:&lt;/strong&gt; este post está obsoleto. Hoje uso &lt;a href="https://github.com/akitaonrails/aitrepreneur-docker"target="_blank" rel="noopener"&gt;&lt;strong&gt;akitaonrails/aitrepreneur-docker&lt;/strong&gt;&lt;/a&gt;, que roda as stacks do Aitrepreneur localmente em Docker com modelos e workflows pré-carregados. Veja o README para detalhes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No &lt;a href="https://www.akitaonrails.com/2025/04/23/usando-i-a-comfyui-pra-gerar-npcs-em-desenvolvimento-de-games"target="_blank" rel="noopener"&gt;post anterior&lt;/a&gt; mostrei como gerar character sheets pra desenvolvimento de games. Mas alguns me perguntaram &amp;ldquo;e se eu quiser roupas específicas?&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hold my Beer&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tem como, é um workflow separado que depois dá pra adicionar ao workflow anterior se quiser (fica de lição de casa). Basta pegar o personagem gerado no primeiro grupo de nodes e passar por este outro workflow que troca roupas. (ou fazer o trabalhinho extra de gerar loras e ativar com palavras-chave no prompt também, não existe só um jeito).&lt;/p&gt;</description><content:encoded><![CDATA[<blockquote>
  <p><strong>Atualização:</strong> este post está obsoleto. Hoje uso <a href="https://github.com/akitaonrails/aitrepreneur-docker"target="_blank" rel="noopener"><strong>akitaonrails/aitrepreneur-docker</strong></a>, que roda as stacks do Aitrepreneur localmente em Docker com modelos e workflows pré-carregados. Veja o README para detalhes.</p>

</blockquote>
<p>No <a href="https://www.akitaonrails.com/2025/04/23/usando-i-a-comfyui-pra-gerar-npcs-em-desenvolvimento-de-games"target="_blank" rel="noopener">post anterior</a> mostrei como gerar character sheets pra desenvolvimento de games. Mas alguns me perguntaram &ldquo;e se eu quiser roupas específicas?&rdquo;.</p>
<p><strong>Hold my Beer</strong></p>
<p>Tem como, é um workflow separado que depois dá pra adicionar ao workflow anterior se quiser (fica de lição de casa). Basta pegar o personagem gerado no primeiro grupo de nodes e passar por este outro workflow que troca roupas. (ou fazer o trabalhinho extra de gerar loras e ativar com palavras-chave no prompt também, não existe só um jeito).</p>
<p>Diferente do workflow do Mick - que é pago - este outro é aberto, peguei em algum Reddit e coloquei na pasta de workflows do meu projeto de Docker como &ldquo;garmente-replacement-idm-vton.json&rdquo;, só carregar no ComfyUI e brincar. Vamos ver o resultado. Eis duas imagens, o resultado não é perfeito, mas veja se descobre qual dos dois é o original e qual é o gerado pela I.A.:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/6b56mdoiwf82xdh7cdhdc7tg0ic2" alt="Hemsworth 1"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/w0s9nthw6jt86en8wl09m67ryzpc" alt="Hemsworth 2"  loading="lazy" /></p>
<p>Resposta ao final do post kkkk</p>
<p>Mas vamos lá, o workflow completo se parece com isto:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xbgu7dzrjzflhfsld6jtrl5jc3ia" alt="workflow"  loading="lazy" /></p>
<p>No estágio atual da minha setup tem um glitch que não consegui resolver. Eu acho que é a extension JNodes que está abrindo um popup infernal no top esquerdo da interface, que não fecha de jeito nenhum. Por enquanto estou abrindo o Inspector e deletando o Node do HTML direto mesmo kkkk gambiarra mas funciona:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/p8q4hmwjmi4opsu8u045jxrpma2v" alt="JNode bug"  loading="lazy" /></p>
<p>Se alguém souber como consertar, agradeceria muito. Tem um alert de uma extension que parece que tá defasada, esse front-end alguma coisa, mas é só ignorar. Tinha que ser front-end-alguma-coisa &hellip;</p>
<p>Enfim. neste primeiro grupo, é simples, basta carregar uma imagem o mais limpa e clara possível da roupa nova que quer usar. E depois a foto original onde quer que essa roupa seja aplicada. Ele não faz milagre, se a foto for de alguém com paletó e você quiser mudar pra regata, às vezes ele consegue, mas não espere que os braços e torso sejam bons, ele vai redesenhar tudo como achar que precisa, ou vai acabar colocando a regata em cima do paletó, enfim, é I.A., quanto mais claro for o objetivo, melhor o resultado. Se a nova roupa é um paletó, idealmente o original era outro paletó, vai encaixar melhor.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2owpldebsc91y6vfsayscdkuqtui" alt="Load Images"  loading="lazy" /></p>
<p>Já avisando que os modelos do IDM-VTON pesam realmente uma <strong>TONELADA</strong>. Ele sozinho baixa nada menos que <strong>30GB</strong> de modelos (já incluso no meu setup) e vai exigir mais de 20GB de VRAM, então abaixo de uma RTX 3090 de 24GB, não vai rodar. Tem como rodar online, no site do Hugging Face. Assista este tutorial que mostra como:</p>
<!-- youtube video unavailable (deleted/blocked): WL59FqL0L-s -->
<p>Esse é o canal do <strong>Aiconomist</strong>. Ele mostra como rodar online e como fazer o setup na sua máquina <a href="https://youtu.be/pFYqPf1Z7x8?si=Ryu5vxPmKGr4zhWx"target="_blank" rel="noopener">neste outro video</a> que usei de referência pra fazer meu workflow. Assinem o canal dele, vale a pena, os tutoriais são bem explicadinhos e tem mais detalhes que não vou explicar neste post.</p>
<p>Isso dito, tem um bug que ainda não resolvi. Depois que roda o workflow, o modelo fica pendurado na VRAM da GPU e se tento rodar de novo, ele EMPERRA. Aí tenho que desligar o container de Docker e reiniciar pra recuperar a memória. Tentei um node de Clear VRAM GPU mas não funcionou. Se alguém souber como consertar, agradeço também.</p>
<p>O próximo passo é recorrente em vários workflow: extrair camadas de informações da foto original. Conseguir uma máscara da roupa que queremos substituir e informações da pose original:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/kgs8jj5m7cgt2nx4kv8yazfpbfg5" alt="máscara e pose"  loading="lazy" /></p>
<p>É por isso que workflows de ComfyUI são poderosos quando você começa a entender como processo é dividido. Essas partes são reusáveis e diferentes modelos dão diferentes resultados dependendo dessas controlnets que se adiciona.</p>
<p>Aí chegamos na parte principal: ligar todas essas informações no node de IDM-VTON, que vai reposicionar a nova roupa em cima da foto original:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/7xnxbrcy22trdctbf5a1dhh0rqjw" alt="IDM-VTON"  loading="lazy" /></p>
<p>Note que o resultado não é muito bom, fica bem &ldquo;fake&rdquo; mesmo. E tem como ajustar parâmetros pra melhorar. Mas o caso de uso não é usar pra trocar por roupa <strong>específica</strong> mas sim por roupa <strong>similar</strong>. Já vou explicar, mas antes, uma tangente.</p>
<p>O repositório da extension original desse Node é <a href="https://github.com/TemryL/ComfyUI-IDM-VTON"target="_blank" rel="noopener">este</a> Mas se tentar instalar, ele quebra. Parece que ele está um pouco defasado com o ComfyUI e precisa rodar numa versão mais velha. Mas isso é um saco. Então, eu resolvi fazer um <a href="https://github.com/akitaonrails/ComfyUI-IDM-VTON"target="_blank" rel="noopener"><strong>FORK</strong></a> com as correções necessárias pra rodar na versão mais nova (as versões mais novas dos pacotes de Python diffusers, transformers, huggingface_hub, quebram o IDM-VTON).</p>
<p>Aliás, o gerenciamento de dependências de Python é bem RUIM. Mas do ComfyUI é ainda pior, porque diferentes extensions (que instalam dependências de Python globalmente) podem estar em diferentes estágios de desenvolvimentos. Toda vez que o ComfyUI em si muda, todo mundo precisa atualizar rápido. Por isso o ideal é não instalar a versão da branch master do ComfyUI, sempre uma versão estável (marcada numa tag) ou duas versões pra trás, pra garantir. Mas aí tem extensions novas que exigem você estar na versão mais nova. Como falei, é um pesadelo pra gerenciar. Sendo programador, eu consigo &ldquo;gambiarrar&rdquo; quando dá erro, mas usuários normais vão sofrer um pouco.</p>
<p>Feito o rant. No passo anterior a primeira parte do fluxo acaba. Quando bate nesse node Image Sender, o processo pára - não sei porque. E precisa clicar no botão &ldquo;RUN&rdquo; uma segunda vez. Daí o outro node, Image Receiver, vai receber a nova imagem. Acho que vou só apagar esses dois nós e ligar direto o output &ldquo;image&rdquo; do node Run IDM-VTON Inference e ligar direto no próximo node que são o Image Composite Masked e o VAE Encode.</p>
<p>Vamos voltar um passo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jj950u7842rkc9lxniz3uoztbszv" alt="Nova foto, nova roupa"  loading="lazy" /></p>
<p>Nesse próximo passo, no segundo &ldquo;RUN&rdquo;, ele pega a imagem com a nova roupa, mas baixa qualidade e carrega um novo checkpoint (no caso o juggernaultXL mas poderia ser qualquer outro como o próprio SDXL, FLUX ou dreamsharper). O modelo vai determinar a nova foto, que é o que se gera no final, na direita.</p>
<p>Note que a nova foto tem qualidade de roupa muito melhor, mas a cara é completamente diferente. Ele usou as informações da foto original, manteve a pose, a proporção do corpo, até o fundo, mas o rosto ele não consegue manter. Não tem problema, porque podemos ajudar.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/bw9sram1maspiob2ubo4r904oriy" alt="mix photos"  loading="lazy" /></p>
<p>Lembra que no passo anterior conseguimos a máscara (preto e branco) da posição exata da roupa? Podemos aumentar a máscara e aplicar um Gaussian Blur (pra depois ele mesclar bonitinho na foto original) e usar isso pra recortar da foto gerada com o rosto diferente, usando o modelo <a href="https://github.com/tencent-ailab/IP-Adapter/"target="_blank" rel="noopener"><strong>IPAdapter</strong></a> (que sozinho são outros 30GB de modelos - falei que é pesado).</p>
<p>Se não entendi errado, IP Adapter é o responsável por conseguir gerar novas imagens COERENTES entre si, com o mesmo rosto, ou mesmas roupas, mas em outras posições, por exemplo, um tipo de geração condicionada. Com ele é possível controlar BASTANTE a coerência de novas imagens, gerar uma personagem em diferentes posições, em diferentes fundos e sempre mantendo as características da personagem original.</p>
<p>O IP Adapter vai usar a foto original, manter o rosto, usar as máscaras e misturar com a foto com o rosto diferente, tirando só a roupa e depois juntar tudo no Image Composite Masked que é o Node final do processo.</p>
<p>No final, podemos comparar a primeira tentativa de colocar a roupa nova (que ficou ok mas feita, e a roupa nova, que ficou diferente mas mais bonita) e tudo integrado na foto original, mantendo o mesmo rosto.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/uawsqhhr6d8kii8getzkq6ebxgh9" alt="image compare"  loading="lazy" /></p>
<p>Como falei, as qualidades são afetadas pela escolha do modelo, as configurações do KSampler, número de passos, cfg e tudo mais. Tem que experimentar e gerar várias até chegar no resultado que te agrada mais. É assim que se aprende mais também. Não é só clicar uma vez e magicamente já ter o resultado. Um bom artista experimenta dezenas de vezes.</p>
<p>A imagem final talvez não seja adequada pra ser capa da Vogue (embora eu já tenha visto muita capa oficial BEM duvidosa). Vai depender da necessidade. Em particular é bom pra prototipação. &ldquo;Como será que fica essa roupa vestida na minha modelo?&rdquo; Em vez de chamar a modelo, mandar vestir, ou gastar uma hora Photoshopando, em minutos dá pra experimentar várias roupas diferentes e ver se a modelo combina com a roupa, sem precisar gastar tempo. Pra um fotógrafo só mostrar pro cliente, antes de gastarem dinheiro com estúdio, ajudantes, equipamento, etc. Dá pra visualizar rapidamente e discutir detalhes muito mais rápido.</p>
<p>É nesse tipo de coisa que ferramentas como essa podem ajudar muito e diferenciar um artista do outro, não substituindo o artista, mas criando mais opções. E o principal: todos esses workflows são modificáveis, você reprograma e combina com outros como quiser. O limite é a criatividade.</p>
]]></content:encoded><category>midia-generativa</category><category>modelos-locais</category></item><item><title>Usando I.A (ComfyUI) pra gerar NPCs em desenvolvimento de games</title><link>https://akitaonrails.github.io/2025/04/23/usando-i-a-comfyui-pra-gerar-npcs-em-desenvolvimento-de-games/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/23/usando-i-a-comfyui-pra-gerar-npcs-em-desenvolvimento-de-games/</guid><pubDate>Wed, 23 Apr 2025 16:25:00 -0300</pubDate><description>&lt;p&gt;Eu andei atualizando BASTANTE meu projetinho de Comfyui com Docker Compose. Eu acho que é o setup mais completo e testado de todos, com a vantagem de ser em Docker, então é repetitível com zero setup. Leia &lt;a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded"target="_blank" rel="noopener"&gt;meu post&lt;/a&gt; pra mais detalhes sobre como ele funciona.&lt;/p&gt;
&lt;p&gt;Resolvi documentar neste post sobre um dos workflow mais complexos que eu testei que funciona nesse meu setup: como desenvolver Character Sheets de personagens aleatórios, em qualquer estilo, seja realista, seja estilo pixar, seja estilo cartoon, o que você quiser. O resultado é um sheet como no exemplo abaixo. Em várias poses, várias iluminações, várias expressões de emoção, com índice de cor, tudo gerado do absoluto zero pela &amp;ldquo;I.A.&amp;rdquo;. Em meia hora de processamento na minha RTX 4090.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Eu andei atualizando BASTANTE meu projetinho de Comfyui com Docker Compose. Eu acho que é o setup mais completo e testado de todos, com a vantagem de ser em Docker, então é repetitível com zero setup. Leia <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded"target="_blank" rel="noopener">meu post</a> pra mais detalhes sobre como ele funciona.</p>
<p>Resolvi documentar neste post sobre um dos workflow mais complexos que eu testei que funciona nesse meu setup: como desenvolver Character Sheets de personagens aleatórios, em qualquer estilo, seja realista, seja estilo pixar, seja estilo cartoon, o que você quiser. O resultado é um sheet como no exemplo abaixo. Em várias poses, várias iluminações, várias expressões de emoção, com índice de cor, tudo gerado do absoluto zero pela &ldquo;I.A.&rdquo;. Em meia hora de processamento na minha RTX 4090.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/469eyg4a4pl3ry0z12twg74nlbc8" alt="NPC Design Sheet"  loading="lazy" /></p>
<p>Imagine que você é estag em estúdio de jogos. Seu trabalho: criar 100 personagens de fundo (NPCs). No fim tem que gerar 100 desses. Mas só porque é personagem secundário, não pode fazer de qualquer jeito, tem que ser todos coerentes, em todas as poses. Imagina quantos dias você iria levar. E se desse pra automatizar? É pra esse tipo de cenário que I.A. é interessante. Gaste mais tempo nos personagens principais, faça manualmente, mas não perca tempo nos secundários e não baixe a qualidade final de tudo.</p>
<p>Eu não fiz esse workflow, quem fez foi o Mickmumpitz. Assista o video inteiro dele pra saber como funciona esse workflow, não vou repetir o que ele já explica:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/grtmiWbmvv0"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Também não vou compartilhar o workflow dele, porque ele tem um Patreon pago (é super barato, só USD 10 por mês), que te dá acesso a baixar o pacote de workflow inteiro dele <a href="https://www.patreon.com/posts/free-workflows-120405048"target="_blank" rel="noopener">neste link</a></p>
<p>Mas só isso não adianta, os requerimentos que você precisa:</p>
<ul>
<li>uma máquina parruda com pelo menos uma RTX 3090 com 24GB de VRAM. Importante aqui é VRAM, os modelos usados fácil ultrapassam uso de 20GB. CPU é menos importante, eu uso uma 7950X3D mas uma 7800 já daria conta.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/730safwxqkrsgawu4liw42scoul3" alt="GPU VRAM"  loading="lazy" /></p>
<ul>
<li>caso não tenha máquina assim, dá mais trabalho mas você pode alugar uma na <strong>RUNPOD</strong>. Eles tem várias configurações de GPU, mas você vai precisar saber configurar um pouco Linux ou escolher Windows e fazer o tutorial do Mickmumptiz tudo na mão (vai por mim, você vai sofrer se fizer assim).</li>
<li>precisa ter pelo menos <strong>500GB</strong> de espaço disponível e internet boa pra baixar. Na primeira vez que meu Docker rodar, ele baixa automaticamente todas as extensions de ComfyUI e todos os modelos pra tudo. Dá pra editar o arquivo <code>models.conf</code> antes e remover modelos que sabe que nunca vai usar. Mas só este workflow exige pelo menos uns 30GB de modelos, não tem como fugir disso.</li>
<li>esse workflow é particularmente <strong>PESADO</strong> porque são várias funcionalidades (que podemos desligar partes, pra customizar pro nosso fluxo de trabalho). Mas pra 1 personagem, o fluxo inteiro pode levar <strong>30 MINUTOS</strong>. Mesmo numa RTX 4090 como a minha.</li>
</ul>
<p>Se fez tudo certo até este ponto, tem ComfyUI rodando com todas as dependências já baixadas e comprou o Workflow. Quando carregar vai ter que consertar algumas coisas manualmente.</p>
<p>Primeira coisa é que vai ver um erro em vários Nodes em vermelho, todos de <strong>Ultimate SD Upscale</strong>, basta clicar com o botão direito do mouse em cada um e escolher &ldquo;Fix Recreate&rdquo; que vai consertar automaticamente:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/djblur5g36mw9denng0ht0vvksym" alt="Fix Upscale"  loading="lazy" /></p>
<p>O ComfyUI exige que cada slot de output de um Node se ligue no slot de input de outro Node compatível, mas frequentemente os workflow ficam tão grandes que tem fiozinho conectando coisa espalhado pra tudo que é canto e fica bem difícil de saber o que liga no que. Literalmente fica <strong>macarrônico</strong>.</p>
<p>Mas existe uma extension (que eu já deixei pré-instalado) chamado <a href="https://github.com/chrisgoringe/cg-use-everywhere"target="_blank" rel="noopener">cg-use-everywhere</a>. Toda extension é um projetinho do GitHub, clonado no sub-diretório <code>custom_nodes</code>. Ele permite conectar a saída de Node nesse node <strong>Anything Elsewhere</strong>, digamos a saída &ldquo;clip&rdquo; do Node &ldquo;Load Clip&rdquo;. Agora, todo outro Node que tiver como input o campo &ldquo;clip&rdquo; e não tiver nada conectando explicitamente nele, vai usar esse Anything que foi criado. Funciona super bem.</p>
<p>Só nesse workflow do Mick que, sei lá porque, todos os Anywheres estão quebrados. Mas é fácil consertar. Basta procurar esses Nodes, apagar, criar novos e reconectar. Tem vários logo no começo, anote quem liga em cada um, apague e crie um novo pra reconectar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/kjjo7u6g58drww1v5f8v88h1iis1" alt="anywhere initial"  loading="lazy" /></p>
<p>Tem também alguns no grupo 02 de Upscale:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qrkdxs6qjdtmdjs3c463oxetbgyy" alt="anywhere upscale"  loading="lazy" /></p>
<p>Nodes pequenos, só com o título, estão COLAPSADOS. Só clicar com o botão direito e escolher EXPAND pra ver os detalhes. É outra forma de organizar a bagunça.</p>
<p>Por último, tem que rechecar o nome correto dos modelos em todos os Nodes de &ldquo;Load&rdquo; alguma coisa logo no começo do workflow (ainda bem que ele organizou esses nodes tudo junto):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/y1u93ujz3kq4hz3pik5q4wcgapoa" alt="model path"  loading="lazy" /></p>
<p>No workflow dele, o Node vem procurando por &ldquo;FLUX/flux1-dev-fp8.safetensors&rdquo; mas aí você vai na lista (que puxa o que está mesmo no diretório &ldquo;models/checkpoints&rdquo; na sua máquina) e escolhe o que tem o mesmo nome. Isso acontece frequentemente porque diferentes pessoas organizam sub-diretórios diferentes pros modelos. Mas depois de configurar é só salvar e pronto. Tem que fazer isso pro checkpoint, controlnet, ic-light, etc.</p>
<p>Os erros do ComfyUI não são amigáveis. São stacktraces de Python, que não são amigáveis porque ele mostra que linha do código deu pau, mas não mostra o que tinha nas variáveis passadas pra função que deu pau. Daí não dá pra saber onde foi. Mas normalmente na interface web, o Node fica em vermelho, daí você checa e normalmente é porque esqueceu de baixar o modelo ou escolher o certo da lista.</p>
<p>Última coisa nesse workflow é baixar a image de OpenPose da página de Patreon dele, que linkei no começo do post, e carregar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/uajcc6ocnr5llzv8wzoowl85c5o3" alt="openpose image"  loading="lazy" /></p>
<p><strong>OpenPose</strong> é um formato de arquivo padrão que descreve poses, formato aberto que pode ser editar em várias ferramentas. E tem diversos modelos pré-treinados pra extrair poses nesse formato a partir de imagens, então nem precisamos editar na mão. Podemos baixar várias poses pré-prontas e abertas neste site:</p>
<p><a href="https://openposes.com/"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ntvaf9vba1rugg11qn3n14u2t69e" alt="Poses Sites"  loading="lazy" /></a>
Vou repetir várias vezes. mas <strong>assistam o video tutorial do Mick</strong> onde ele explica como mandar gerar personagens do zero ou usar <strong>suas próprias fotos</strong> com suas próprias poses pra continuar o fluxo a partir disso. O workflow dele, depois que roda, tem esta cara (com zoom lá embaixo):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jqu4o2c5cenci6au2g8bbny064mm" alt="complete workflow"  loading="lazy" /></p>
<p>Na primeira parte, tem a configuração e checagem manual dos modelos. Só tem que arrumar uma vez e salvar. Depois sempre vai funcionar igual. Nesse primeiro menu também dá pra ligar e desligar alguns dos grupos de Nodes, por exemplo, pra pular a primeira etapa de gerar personagem do zero e poder usar suas próprias fotos:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/s72mrc1ib5o173c9l74vmpsn3js3" alt="menu and setup"  loading="lazy" /></p>
<p>Se optar por gerar personagem do zero, tem em verde uma caixa de prompt pra digitar como quer que seja sua personagem, em que estilo. Prompts de Texto são decodificados por modelos de <strong>CLIP</strong> (Contrastive Language Image Pre-Training), que são específicos pra determinadas categorias de UNETs (como SDXL pra Stable Diffusion ou o melhor e mais popular FLUX agora). Parte do aprendizado é começar a aprender quais sub-modelos (clips, clip_vision, vaes, loras, etc) são compatíveis com quais unets (unet, checkpoints, diffusion_models). Não pode misturar qualquer coisa. Grosseiramente, é meio que se você escolhe fazer um texto em alemão, todos os plugins tem que ser em alemão.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/37oe4dyqr5t0in9ad98bocli6u3w" alt="Character Generation"  loading="lazy" /></p>
<p>Não vou mostrar o workflow completo porque nem cabe, mas dá pra ver em verde os prompts, no meio onde escolhemos que pode queremos (tá no Patreon dele), e em cima a image gerada pelo modelo <strong>flux1-dev-fp8</strong>, um dos melhores e mais populares por ter excelente qualidade e não ser tão pesado (fp8 em vez de fp32, por exemplo).</p>
<p>Isso é uma das vantagens de usar ferramentas fora dos Dalle ou Midjourney da vida: podemos controlar todos os aspectos da geração da imagem não só com prompts de texto, mas com prompts de imagens, como o OpenPose.</p>
<p>Outro detalhe. Expliquei no meu post anterior sobre KSampler ou Samplers em geral, que são o motor que faz todo o trabalho de juntar todos os modelos, imagens, prompts, controlnet, vaes e produz a imagem final. Os parâmetros do KSampler como steps, cfg, denoise, scheduler e mais <strong>DEPENDEM</strong> do modelo escolhido. Tem modelos bem documentados, tem modelos mal documentados.</p>
<p>Por exemplo, digamos que você leu numa thread de Reddit sobre esse outro modelo mais otimizado chamado <a href="https://civitai.com/models/215418?modelVersionId=273102"target="_blank" rel="noopener">&ldquo;TurboVisionXL&rdquo;</a>, que está disponível no site Civit.ai:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/hpcj5e8g0hxvefelvszwf48vsnpf" alt="TurboVisionXL"  loading="lazy" /></p>
<p>Realmente é um bom modelo mas e agora? O que eu configuro no KSampler pra ele? Felizmente se scrolar a página (sempre leia a documentação original!!) vai encontrar estas recomendações:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/57f9vyfh4w9j8bduaomc3aow2sks" alt="TurboVisionXL documentation"  loading="lazy" /></p>
<p>Viu só? Ele explica o que configurar e quais são limitações conhecidas. Mais embaixo vai ter comentários de outras pessoas que podem dar mais dicas ou compartilhar erros que você já tenha visto. É uma enorme comunidade, precisa aprender a participar dela.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/7avk9tl1pop1eexkuu3h8epgs2t5" alt="MultiView"  loading="lazy" /></p>
<p>Mas voltando da tangente, no segundo grupo de nodes do Mick, ele pega a primeira imagem em &ldquo;Pose T&rdquo; e passa por um processo de &ldquo;Multi View&rdquo; usando Diffuser MV que faz isso automaticamente. É a mesma pose, mas como se a câmera desse um 360 graus ao redor dela, pegando ângulos diferentes e, principalmente, gerando novas imagens coerentes com a original (veja como o modelo é usado até pra inventar as costas da personagem).</p>
<p>Nesse passo também ele extrai só o rosto da primeira imagem pra fazer outros tratamentos em paralelo. Reconhecimento e extração de rostos é super comum. Todo sistema de segurança usa, Google Photos usa pra organizar álbuns dinâmicos. E vale você aprender pra usar em seus produtos. É um processo razoavelmente leve e rápido hoje em dia e tem vários algoritmos e modelos pré-prontos pra usar. Nesse caso acho que nem usa I.A. propriamente dito, é um algoritmo mesmo.</p>
<p>Outro detalhe: modelos de geração de imagens normalmente trabalham em <strong>BAIXA RESOLUÇÃO</strong>. Normalmente abaixo de 1024x1024. Não espere gerar imagens 4K logo de cara. Mas não tem problema, porque a solução é primeiro gerar uma image de baixa resolução e depois passar por outro modelo de I.A. especializado em <strong>UPSCALING</strong> como o RealESRGAN que já falei no meu outro post sobre <a href="https://www.akitaonrails.com/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a"target="_blank" rel="noopener">upscaling de anime</a></p>
<p>Diferente do que faz um Photoshop ou qualquer editor de imagens, se tentar aumentar uma imagem, ele só vai pegar um pixel e tentar duplicar, quadruplicar os pixels ao redor. Mas isso costuma só deixar a imagem maior mas borrada (blurred), porque ele não tem como saber os detalhes, e nem vai tentar &ldquo;inventar&rdquo; detalhes.</p>
<p>Pra &ldquo;inventar&rdquo; detalhes, precisa de um modelo pré-treinado que aprendeu o que desenhar. É como funciona também o DLSS 3 da NVIDIA pra games, onde o jogo renderiza em baixa resolução, como 720p e o DLSS, em tempo real, redesenha a nova tela em 4K. Não fica perfeito, mas jogando a 60fps, você raramente vai notar a diferença. Só vai perceber que ficou mais <strong>NÍTIDO</strong>.</p>
<p>Por isso também, se você usar sua própria foto, ou foto de pessoas conhecidas, vai ver que a nova foto depois de upscaling ficou <strong>levemente diferente</strong>, não é mais a mesma pessoa, porque o modelo <strong>REDESENHA</strong> detalhes que não existiam. É um processo de interpretação e não de descobrir detalhes &ldquo;escondidos&rdquo; (isso não existe, só na ficção de CSI).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2he2fgmus8vmp3wfp95s3qbh7ztf" alt="Upscale 2"  loading="lazy" /></p>
<p>O workflow do Mick vai um passo além e tem uma segunda passada de upscaling, focando em consertar somente o rosto om um Node de Face Detailer, feito especificamente pra consertar e melhorar rostos. E aqui entramos em território de cirurgia plástica e harmonização mesmo kkkk o rosto final vai ser diferente do original. Mas pra NPC e personagens secundários não importa, ninguém vai saber a diferença mesmo, basta não estar deformado, zarolho ou algo assim que salta à vista.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/3d40a8n9b32mmkub5l8c04oou37h" alt="Emotions"  loading="lazy" /></p>
<p>Existem Nodes especializados em expressões, como o Expression Editor (PHM) que parte de uma única foto de rosto e consegue gerar novas fotos nas mais variadas emoções - que podemos configurar exatamente, numericamente, como queremos. Não vou mostrar, mas note que as imagens geradas estão em baixa qualidade, mas o próximo passo do workflow é justamente fazer upscaling delas também.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/cxdua4dfalu6d68tpaleljie5y1e" alt="Lighting"  loading="lazy" /></p>
<p>Esse passo é interessante. Primeiro a parte de cima, com os vários cenários. Eles foram todos gerados via os prompts em verde abaixo deles, prompts que o Mick deixou mas podemos editar como quisermos OU trocar o node de geração e colocar de Load Image pra carregar nossas próprias imagens de fundo, de fotos de verdade.</p>
<p>O objetivo desse passo é gerar os rostos sob diferentes configurações de luz. Muita gente pensa que &ldquo;luz&rdquo; em jogos é só &ldquo;acender a luz vermelha&rdquo; e todo mundo vai ficar vermelho automaticamente. Em jogos novos, com <strong>RAY TRACING</strong> sim, por isso é SUPER PESADO. Mas em jogos feitos pra máquinas antigas, jogos indie, não dá pra exigir isso de todo mundo.</p>
<p>Daí se usam &ldquo;truques&rdquo; pra falsificar coisas como luzes e sombra. É o processo de <strong>BAKING</strong> onde literalmente &ldquo;estampamos a luz na textura&rdquo; dos objetos e personagens. Com a luz global ligada ou desligada, eles sempre vão estar &ldquo;pintados&rdquo; na luz colorida da cena. Pra mudar de cena, carregam novas texturas com outras luzes &ldquo;baked&rdquo;. Pra programadores, pense em baking como <strong>Caching</strong>. Pra que toda vez recalcular tudo dinamicamente, se a luz da cena nunca muda? Economize esse passo e já deixe &ldquo;cacheado&rdquo;.</p>
<p>Voltando, esse passo usa a tecnologia <a href="https://github.com/lllyasviel/IC-Light"target="_blank" rel="noopener"><strong>IC-Light</strong></a> que é outro projeto open source que vale aprender, mesmo se não se interessa por I.A. Ele consegue manipular a luz de qualquer imagem ou foto. Você, como fotógrafo, pode mudar a luz caso tenha mudado de idéia, em pós-produção. Ferramentas da Adobe ou BlackMagic, acho que já incluem tecnologia similar, mas é bom saber que existe open source e dá pra integrar no seu produto.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qup2n6vadpc7qrtgokbqbfkpvmjg" alt="final sheet"  loading="lazy" /></p>
<p>Depois de gerar os rostos com diferentes luzes, obviamente tem outro passo de upscaling - que estou pulando de mostrar - e vai pro grupo de nodes final que junta tudo num &ldquo;sheet&rdquo;, uma &ldquo;folha do personagem&rdquo;. O desenvolvedor de jogos pode usar direto num jogo 2D (pense um visual novel game da vida), ou pode servir de base pra UV Mapping de texturas de um modelo 3D simples com Armature pra animar e já tem alguns rostos de emoções. Bom pra colocar no cenário de fundo, pedestres andando na rua, fazendo compras, sentados na praça ou algo assim.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/cgv0qp3slipgw4nrlgnrserl1n8s" alt="Loras"  loading="lazy" /></p>
<p>Além disso, o tutorial do Mick descreve como, é possível criar <strong>LORAS</strong> (Low-Rank Adaptation). Se quiser conseguir gerar personagens com estilo ou mesmo rostos específicos (sem intervenção &ldquo;artística&rdquo; da I.A.), você escolhe um modelo como FLUX ou SDXL e &ldquo;em cima&rdquo; dele adiciona um segundo sub-modelo compatível com informações sobre essa pessoa ou personagem.</p>
<p>Pra isso você tira várias fotos limpas e bem iluminadas da pessoas, nos mais diferentes ângulos, nem precisa de muito, só uma dúzia é suficiente. Pra cada imagem cria prompts que descrevam bem, e é isso. Tem ferramentas pra gerar esses Loras localmente ou sites que fazem isso (assista o video).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/tk8tbtrr1ealm4c4eu26alk63xlh" alt="Creating Prompts"  loading="lazy" /></p>
<p>Falando em prompts, em vários workflows existem passos pra descrever prompts detalhados de imagens geradas dinamicamente. Em vez de exigir que o usuário (você) faça isso manualmente, é possível usar um modelo de decoding, que decodifica imagens em prompts de texto. O modelo mais usado pra isso é o <strong>FLORENCE 2</strong> da Microsoft.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/dwr87ajtkj7w15ftsy86l5u4hz44" alt="Florence 2"  loading="lazy" /></p>
<p>Esse box de texto embaixo é read-only, você não precisa digitar manualmente. O Node de Florence 2 Run vai ler a imagem e gerar esse texto. Veja como ele descreveu a primeira imagem da mulher na pose de &ldquo;T&rdquo;. Fazendo testes, pro seu caso, você determina se só isso é suficiente, se não for basta colocar um segundo Node de prompt e concatenar os dois antes de mandar pra outros nodes.</p>
<p>Enfim, tem MUITA coisa pra explorar só nesse workflow. O Mick fez um excelente trabalho tanto no workflow quanto no video pra explicar e vale a pena pagar os 10 dólares pra contribuir. Existem dezenas de workflows assim disponíveis na Web, basta procurar. E meu setup está cada vez mais complexo. Pretendo ficar ajustando ele pra cada novo workflow interessante que esbarrar. Já tem vários pré-testados na pasta &ldquo;workflows&rdquo; do meu projeto pra você brincar.</p>
]]></content:encoded><category>midia-generativa</category><category>games</category><category>modelos-locais</category></item><item><title>Entendendo o Básico de ComfyUI pra gerar imagens com I.A.</title><link>https://akitaonrails.github.io/2025/04/20/entendendo-o-basico-de-comfyui-pra-gerar-imagens-com-i-a/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/20/entendendo-o-basico-de-comfyui-pra-gerar-imagens-com-i-a/</guid><pubDate>Sun, 20 Apr 2025 19:30:00 -0300</pubDate><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Atualização:&lt;/strong&gt; este post está obsoleto. Substitui a configuração manual por um projeto containerizado mais limpo: &lt;a href="https://github.com/akitaonrails/aitrepreneur-docker"target="_blank" rel="noopener"&gt;&lt;strong&gt;akitaonrails/aitrepreneur-docker&lt;/strong&gt;&lt;/a&gt;. Ele roda as stacks do Aitrepreneur localmente em Docker, com versões fixadas, modelos persistentes e workflows pré-carregados. Veja o README para detalhes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No &lt;a href="https://www.akitaonrails.com/2025/04/20/gerando-imagens-com-i-a-ate-estilo-ghibli-com-docker-e-cuda"target="_blank" rel="noopener"&gt;post anterior&lt;/a&gt; expliquei meu projetinho de como subir ComfyUI pré-configurado e pré-carregado automaticamente usando Docker Compose. Assumindo que você já tem tudo de pé, agora é entender essa desgraça do ComfyUI um pouco.&lt;/p&gt;</description><content:encoded><![CDATA[<blockquote>
  <p><strong>Atualização:</strong> este post está obsoleto. Substitui a configuração manual por um projeto containerizado mais limpo: <a href="https://github.com/akitaonrails/aitrepreneur-docker"target="_blank" rel="noopener"><strong>akitaonrails/aitrepreneur-docker</strong></a>. Ele roda as stacks do Aitrepreneur localmente em Docker, com versões fixadas, modelos persistentes e workflows pré-carregados. Veja o README para detalhes.</p>

</blockquote>
<p>No <a href="https://www.akitaonrails.com/2025/04/20/gerando-imagens-com-i-a-ate-estilo-ghibli-com-docker-e-cuda"target="_blank" rel="noopener">post anterior</a> expliquei meu projetinho de como subir ComfyUI pré-configurado e pré-carregado automaticamente usando Docker Compose. Assumindo que você já tem tudo de pé, agora é entender essa desgraça do ComfyUI um pouco.</p>
<p>Eu também não sou nenhum especialista, mas achei legal explicar alguns conceitos que muitos ignoram.</p>
<h3>Modelos Pré-Treinados<span class="hx:absolute hx:-mt-20" id="modelos-pré-treinados"></span>
    <a href="#modelos-pr%c3%a9-treinados" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Todo GPT da vida, ou Stable Diffusion ou outros carregam um banco de dados vetorial na memória da GPU, na VRAM, pra processar. São modelos GRANDES, chamados de 7B, 70B, 100B, etc. &ldquo;B&rdquo; de &ldquo;Bilhões de Parâmetros&rdquo;. Parâmetros não tem definição usável fora da matemática, são simples números. Esses números representam &ldquo;alguma coisa&rdquo; dentro milhões de dimensões dentro desse arquivão.</p>
<p>Em um jeito BEM GROSSEIRO, eu penso como sendo artefatos de compressão. Você pega PETABYTES de livros, imagens, textos, posts, etc, quebra tudo em tokens e encontra &ldquo;relacionamentos&rdquo; de um token com vários tokens e vai &ldquo;posicionando&rdquo; no espaço, mas não em 3 dimensões, mas milhões de dimensões. Modelos de I.A. são &ldquo;Vector Space Models&rdquo; ou VSMs, mais ou menos como é o índice de um Elasticsearch se você já brincou com procura de texto com relevância, ou qualquer plugin de &ldquo;Full Text Search&rdquo; de um Postgres ou MS-SQL. São todos VSMs.</p>
<p>Eu penso assim: é como se o processo de treinamento - a parte SUPER CARA, que leva MESES - pegasse toda informação que é passada e compilasse um novo &ldquo;dicionário&rdquo; pra uma nova &ldquo;língua&rdquo;. Agora, quando você &ldquo;fala&rdquo; com um GPT, primeiro ele precisa transformar seu texto da sua língua pra essa &ldquo;língua interna&rdquo; dele, isso é o que chamamos de um <strong>&ldquo;Embedding&rdquo;</strong>.</p>
<p>Qualquer dado pode ser transformado. Áudio pode ser convertido em texto e de texto em embeddings. Videos podem ser desmonstados em séries de imagens com informação temporal. Imagens podem ser desconstruídas em elementos como luminância, crominância, etc. No fim, tudo pode ser desconstruído em estruturas de dados, não tem mágica.</p>
<p>Modelos de Geração de Imagem são treinados diferente. Tem diversos focos, foco em nitidez de rostos, foco em exatidão de poses, foco em estilos diferentes como cartoon ou pintura, foco em categorias como animais ou objetos. Por isso tem dezenas de modelos por aí e diversos &ldquo;loras&rdquo;.</p>
<p>Pra gerar uma imagem a partir de um prompt, primeiro precisa decodificar seu prompt num embedding, pra isso servem coisas como um <strong>TEXT ENCODER</strong> no ComfyUI.</p>
<p>No caso específico de I.A. pra imagens, eu entendo que se usa um tal de modelo de &ldquo;DIFUSÃO&rdquo; tanto pra desestruturar a imagem original, quanto pra reconstruir uma nova imagem. Não é intuitivo, mas a reconstrução não começa numa tela branca, como seria pra um desenhista humano. Ele começa com uma imagem com barulho aleatório &ldquo;noise&rdquo; tipo isso aqui:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/rcvhb8m0boe3jetpy72kww2wmucw" alt="noise"  loading="lazy" /></p>
<p>O modelo começa desse barulho e via um tal processo de DIFUSÃO começa a redesenhar a imagem de trás pra frente, até sair do caos e chegar numa imagem.</p>
<p>O que eu &ldquo;ACHO&rdquo;, &ldquo;CHUTO&rdquo; que aconteça - sem ter lido paper nenhum, preguiça mesmo - é que tentamos influenciar esse barulho inicial. Por exemplo, extraindo funcionalidades da imagem como um mapa de profundidade, que se parece com isso:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qj0ba39q5xbs7xyei52h4qmmpr2d" alt="Mapa de Profundidade"  loading="lazy" /></p>
<p>A foto original era assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/6zionu0khn01gpudd9z0ws2mm8zx" alt="Original"  loading="lazy" />.jpg)</p>
<p>Extrair um Mapa de Profundidade é um <strong>ALGORITMO</strong> e não &ldquo;I.A.&rdquo;. Qualquer Photoshop, Premiere da vida conseguem fazer isso com uma mão nas costas. Mas é pra explicar que tem muito mais informação numa imagem que só olhando você - que não é treinado - não sabe que existe.</p>
<p>Outro tipo de ALGORITMO bem conhecido é o <strong>Canny Edge Detection</strong> que faz um mapa de bordas, pra ficar mais fácil de saber onde alguma coisa começa e onde termina na imagem:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/std6jaroftohqjlc22mii9m7oso6" alt="Canny"  loading="lazy" /></p>
<p>Então é mais ou menos assim:</p>
<ul>
<li>assim como um texto é &ldquo;convertido&rdquo; numa <strong>Embedding</strong>, vetores que capturam a &ldquo;essência&rdquo; do significado do texto, dependente da VSM do modelo pré-treinado, uma imagem é &ldquo;convertida&rdquo; internamente num <strong>LATENT</strong>, que por ser imagem é um &ldquo;espaço&rdquo; (por isso se fala em Latent Space). Latents não temos como &ldquo;visualizar&rdquo;, porque é uma representação interna que depende da U-NET, o modelo pré-treinado, a rede neural que aprende a &ldquo;tirar barulho&rdquo; (denoise) o Latent.</li>
<li>Uma U-NET de imagens é grosseiramente similar a um modelo de texto como GPT ou LLaMA, o modelo pré-treinado, read-only, que vai ser usado pra produzir o resultado final.</li>
<li>Uma resposta de GPT é uma continuação do contexto da conversa anterior. Ele não está &ldquo;respondendo&rdquo; você diretamente, está só &ldquo;continuando&rdquo; o texto da conversa e por acaso, pelo treinamento, o resultado &ldquo;se parece&rdquo; com uma resposta. No caso de imagens, começamos com uma imagem aleatoriamente barulhenta (noise) e num processo de tirar e adicionar barulho, ele vai &ldquo;reorganizando&rdquo; o Latent numa imagem que conseguimos reconhecer.</li>
<li>Existe um &ldquo;Scheduler&rdquo; que decide quanto barulho é adicionado ou removido a cada passo no tempo. E existe o conceito de <strong>DIFFUSION MODEL</strong> que é mais ou menos o &ldquo;pipeline&rdquo; de processamento.</li>
<li>Podemos controlar o processo adicionando um prompt de texto, que é decodificado numa &ldquo;embedding&rdquo; usando um <strong>Text Decoder</strong> normalmente tem nomes como &ldquo;CLIP&rdquo; ou propriamente um Transformer (como LLava). Esses embeddings vão pra U-NET usando camadas de atenção cruzada, direcionando o &ldquo;denoising&rdquo; em direção ao significado do prompt.</li>
<li>Além disso temos <strong>CONTROLNET</strong> que é uma segunda rede paralela, que adapta o processo pra condições extras. É uma cópia da arquitetura de U-NET (com pesos congelados) que recebem inputs extras, como o Canny Edges, ou Depth Maps, esqueletos de pose (via OpenPose), etc e aprende pequenos ajustes pra <strong>INJETAR</strong> nos mapas principais da U-NET.</li>
<li>Durante o processo de denoising, ativações da ControlNet são mescladas às da U-NET, forçando o processo a respeitar as condições que passamos.</li>
</ul>
<p>Então a U-NET é o &ldquo;gerador&rdquo;, o Diffusion Scheduler orquestra o processo, text encoder e contronet, ambos influenciam a U-NET, e um VAE é usado no final pra converter um espaço latente em uma imagem propriamente dita que nós, humanos, conseguimos enxergar.</p>
<p>BEM, BEM A GROSSO MODO. É mais ou menos como eu entendo o processo. E eu entendo que o ComfyUI expõe vários desses passos na forma de &ldquo;NODES PROGRAMÁVEIS&rdquo;, que podemos parametrizar e ajustar pra influenciar como queremos que a imagem final saia.</p>
<p>Além de parâmetros numéricos, esses Nodes também nos deixam escolher quais arquivos de modelos pré-treinados pra cada etapa queremos usar. É tudo um VSM binário, mas pra utilidades diferentes, por isso muitos tem a mesma extensão como eu expliquei, mas ficam em sub-diretórios diferentes. Pra cada Node conseguir achar só o que lhe interessa.</p>
<p>Pegando o exemplo da minha foto, não é isso mas só pra ilustrar, me ajuda a pensar que aqueles mapas que eu mostrei influenciam a imagem &ldquo;noisy&rdquo; numa direção não-aleatória, como este que eu peguei numa etapa intermediária do workflow que estou rodando:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/yy0yrlrcel015bucfr3z60izt42p" alt="noise"  loading="lazy" /></p>
<p>Se forçar seus olhos, consegue ver influência das imagens anteriores, e o modelo de Difusão vai fazendo o &ldquo;denoising&rdquo;, sendo &ldquo;atrapalhado&rdquo; pela ControlNet. Como se você estivesse desenhando e tivesse uma pessoa do lado que de vez em quando apaga o que você fez, ou desenha por cima, e você vai se adaptando. Então no final vira um trabalho &ldquo;colaborativo&rdquo; entre os dois. Se fosse só a U-NET, ele ia gerar uma nova imagem bem diferente da original, mas a ControlNet é o &ldquo;cliente&rdquo; que fica toda hora de buzinando na orelha &ldquo;não é assim que eu quero, muda&rdquo;, até chegar num resultado aceitável.</p>
<p>Além disso, uma U-NET não é boa pra gerar imagens de altíssima resolução. O ideal é gerar imagens pequenas, 512 pixels, 1024 pixels, mas nada maior. Mas isso seria inútil pra usar profissionalmente. Pra isso servem <strong>UPSCALE MODELS</strong>. Se leu meu artigo de ontem sobre <a href="https://www.akitaonrails.com/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a"target="_blank" rel="noopener">Video2K</a> eu explico que escolho um modelo lá que é bom pra coerência temporal de video. Mas tinha outros modelos que são melhores pra &ldquo;IMAGENS ESTÁTICAS&rdquo;, ou fotos. E podemos usar eles no ComfyUI.</p>
<p>Depois que a nova imagem pequena é gerada pela U-NET, depois podemos usar ela como entrada pra um segundo passo de &ldquo;UPSCALE&rdquo; usando um modelo como RealERSGAN, e sair de uma imagem de 1024 pixels pra 2160 pixels, essencialmente 4K. É diferente de aumentar a imagem num Photoshop, onde fica tudo &ldquo;soft&rdquo; borrado. Como também é outro modelo de I.A. ele é treinado pra &ldquo;redesenhar&rdquo; em mais resolução. E podemos adicionar isso no workflow do ComfyUI também.</p>
<p>Mais do que isso, o ComfyUI também suporta Nodes com modelos como o HunyuanVideo da Tencent, que apresentei no outro artigo de ontem, sobre <a href="https://www.akitaonrails.com/2025/04/19/gerando-videos-de-ate-2-min-a-partir-de-uma-foto-com-i-a"target="_blank" rel="noopener">FramePack</a>. Na verdade, podemos fazer a mesma coisa que o FramePack faz mas a partir de um workflow de ComfyUI: gerar um video a partir de uma imagem, usando o mesmo modelo. O FramePack é infinitamente mais fácil de usar, mas o ComfyUI é a faca suíça de I.A.: ela faz <strong>TUDO</strong>, por isso também é infinitamente mais complicada e precisa se dedicar bastante pra aprender a usar.</p>
<p>Entendendo até aqui, olhem o workflow que eu usei (está com zoom out, dá pra aumentar o zoom dinamicamente):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/rrtdjr0sq5vsl3xnm08w3sz459w8" alt="workflow"  loading="lazy" /></p>
<p>Dá pra ver esses passos intermediários usando Nodes de <strong>PREVIEW</strong> então é mais ou menos assim que podemos usar o ComfyUI pra fazer muita coisa avançada. Como falei, eu mesmo ainda não comecei nem a arranhar a superfície.</p>
<p>Vamos resumir alguns dos conceitos usados no ComfyUI agora:</p>
<h3><strong>1. Checkpoints (Modelos Base)</strong><span class="hx:absolute hx:-mt-20" id="1-checkpoints-modelos-base"></span>
    <a href="#1-checkpoints-modelos-base" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
São os modelos principais do Stable Diffusion (ex: SD 1.5, SDXL, Flux). Contêm toda a arquitetura do modelo:</p>
<ul>
<li>
<p>Text Encoder (CLIP)</p>
</li>
<li>
<p>Modelo de Difusão (U-Net)</p>
</li>
<li>
<p>VAE (Decoder de imagens)</p>
</li>
</ul>
<p><strong>Para que servem</strong>:<br>
Gerar imagens a partir do zero. Cada checkpoint tem um &ldquo;estilo&rdquo; diferente (realista, anime, etc).</p>
<p><strong>Exemplos</strong>:<br>
<code>flux.safetensors</code>, <code>realisticVisionV60.safetensors</code>, <code>anythingV5.safetensors</code></p>
<h3><strong>2. VAE (Autoencoders Variacionais)</strong><span class="hx:absolute hx:-mt-20" id="2-vae-autoencoders-variacionais"></span>
    <a href="#2-vae-autoencoders-variacionais" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Componente que decodifica as imagens do espaço latente (formato que o modelo entende) para pixels visíveis.</p>
<p><strong>Para que servem</strong>:</p>
<ul>
<li>
<p>Melhorar detalhes e cores</p>
</li>
<li>
<p>Alguns checkpoints requerem VAEs específicos</p>
</li>
</ul>
<p><strong>Exemplos</strong>:<br>
<code>vae-ft-mse-840000.safetensors</code>, <code>animevae.pt</code></p>
<h3><strong>3. Text Encoders (Codificadores de Texto)</strong><span class="hx:absolute hx:-mt-20" id="3-text-encoders-codificadores-de-texto"></span>
    <a href="#3-text-encoders-codificadores-de-texto" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Modelos que convertem seu prompt de texto em representações numéricas que a IA entende.</p>
<p><strong>Tipos comuns</strong>:</p>
<ul>
<li>
<p><strong>CLIP</strong>: Padrão para a maioria dos modelos</p>
</li>
<li>
<p><strong>OpenCLIP</strong>: Versão alternativa usada no SDXL</p>
</li>
<li>
<p><strong>T5</strong>: Modelos maiores para prompts complexos</p>
</li>
</ul>
<p><strong>Exemplos</strong>:<br>
<code>clip_vision.safetensors</code>, <code>openclip.safetensors</code></p>
<h3><strong>4. CLIP Vision</strong><span class="hx:absolute hx:-mt-20" id="4-clip-vision"></span>
    <a href="#4-clip-vision" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Modelos especializados em análise visual (ao contrário do CLIP normal que é texto→imagem).</p>
<p><strong>Para que servem</strong>:</p>
<ul>
<li>
<p>Criar descrições de imagens existentes</p>
</li>
<li>
<p>Ferramentas como &ldquo;Image Prompt&rdquo; (usam a imagem como input)</p>
</li>
</ul>
<p><strong>Exemplo</strong>:<br>
<code>clip_vision_g.safetensors</code></p>
<h3><strong>5. Diffusion Models (Modelos de Difusão)</strong><span class="hx:absolute hx:-mt-20" id="5-diffusion-models-modelos-de-difusão"></span>
    <a href="#5-diffusion-models-modelos-de-difus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Parte específica da arquitetura (U-Net) responsável pelo processo de difusão (adição/remoção de &ldquo;ruído&rdquo;).</p>
<p><strong>Para que servem</strong>:</p>
<ul>
<li>
<p>Treinamento de LoRAs</p>
</li>
<li>
<p>Workflows avançados de reutilização</p>
</li>
</ul>
<p><strong>Exemplo</strong>:<br>
<code>unet.safetensors</code></p>
<h3><strong>6. LoRAs (Adaptadores Leves)</strong><span class="hx:absolute hx:-mt-20" id="6-loras-adaptadores-leves"></span>
    <a href="#6-loras-adaptadores-leves" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Pequenos arquivos que modificam o comportamento do checkpoint principal sem substituí-lo.</p>
<p><strong>Para que servem</strong>:</p>
<ul>
<li>
<p>Adicionar estilos específicos (ex: anime)</p>
</li>
<li>
<p>Criar personagens consistentes</p>
</li>
<li>
<p>Ajustar proporções corporais</p>
</li>
</ul>
<p><strong>Exemplo</strong>:<br>
<code>epiNoiseOffset.safetensors</code>, <code>add_detail.safetensors</code></p>
<h3><strong>7. ControlNet</strong><span class="hx:absolute hx:-mt-20" id="7-controlnet"></span>
    <a href="#7-controlnet" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Modelos que impõem controle sobre a composição da imagem (poses, profundidade, bordas).</p>
<p><strong>Tipos comuns</strong>:</p>
<ul>
<li>
<p>OpenPose (poses humanas)</p>
</li>
<li>
<p>Canny (bordas)</p>
</li>
<li>
<p>Depth (profundidade)</p>
</li>
</ul>
<p><strong>Exemplo</strong>:<br>
<code>control_v11p_sd15_openpose.safetensors</code></p>
<h3><strong>8. Upscale Models (Modelos de Upscaling)</strong><span class="hx:absolute hx:-mt-20" id="8-upscale-models-modelos-de-upscaling"></span>
    <a href="#8-upscale-models-modelos-de-upscaling" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Modelos para aumentar resolução e detalhes de imagens geradas.</p>
<p><strong>Tipos</strong>:</p>
<ul>
<li>
<p><strong>ESRGAN</strong>: Para detalhes realistas</p>
</li>
<li>
<p><strong>Anime6B</strong>: Especializado em arte anime</p>
</li>
</ul>
<p><strong>Exemplo</strong>:<br>
<code>4x_NMKD-Superscale-SP_178000_G.pth</code></p>
<h3><strong>9. Embeddings (Textual Inversions)</strong><span class="hx:absolute hx:-mt-20" id="9-embeddings-textual-inversions"></span>
    <a href="#9-embeddings-textual-inversions" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Pequenos arquivos que adicionam conceitos novos ao vocabulário do modelo.</p>
<p><strong>Para que servem</strong>:</p>
<ul>
<li>
<p>Estilos artísticos específicos</p>
</li>
<li>
<p>Objetos personalizados</p>
</li>
<li>
<p>Correções de artefatos</p>
</li>
</ul>
<p><strong>Exemplo</strong>:<br>
<code>bad_prompt.pt</code>, <code>easynegative.safetensors</code></p>
<h3><strong>10. Style Models</strong><span class="hx:absolute hx:-mt-20" id="10-style-models"></span>
    <a href="#10-style-models" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são:</strong><br>
Modelos especializados em aplicar estilos visuais ou “filmes” de arte sobre a imagem gerada, sem alterar de fato a estrutura básica do conteúdo.</p>
<p><strong>Para que servem:</strong></p>
<ul>
<li>
<p><strong>Colorização temática</strong> (ex: paleta de aquarela, sépia, croma‑key)</p>
</li>
<li>
<p><strong>Simular técnicas artísticas</strong> (óleo, guache, traço de mangá)</p>
</li>
<li>
<p><strong>Unificar estética</strong> em séries de imagens (mesmo “clima” de cor e luz)</p>
</li>
</ul>
<p><strong>Exemplo de arquivos:</strong></p>
<ul>
<li>
<p><code>flux1-redux-dev.safetensors</code> (estilo “Flux Redux”)</p>
</li>
<li>
<p><code>watercolor_v2.safetensors</code> (pinceladas de aquarela)</p>
</li>
<li>
<p><code>film_grain_vintage.safetensors</code></p>
</li>
</ul>
<h3><strong>11. SAMs (Segment Anything Models)</strong><span class="hx:absolute hx:-mt-20" id="11-sams-segment-anything-models"></span>
    <a href="#11-sams-segment-anything-models" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são:</strong><br>
Redes de segmentação gerais capazes de isolar objetos ou regiões de interesse em uma imagem, produzindo máscaras binárias ou ponderadas.</p>
<p><strong>Para que servem:</strong></p>
<ul>
<li>
<p><strong>Criar máscaras de objetos/pessoas</strong> para aplicação seletiva de filtros ou composições</p>
</li>
<li>
<p><strong>Guiar ControlNets</strong> (ex: aplicar difusão apenas dentro ou fora de uma região)</p>
</li>
<li>
<p><strong>Pré‑processar referências</strong> (recortar fundo, separar camadas)</p>
</li>
</ul>
<p><strong>Exemplo de arquivo:</strong></p>
<ul>
<li><code>sam_vit_b_01ec64.pth</code> (SAM ViT‑B, modelo base de segmentação)</li>
</ul>
<h3>Resumo Visual<span class="hx:absolute hx:-mt-20" id="resumo-visual"></span>
    <a href="#resumo-visual" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>models/
├── checkpoints/        ➔ Modelos principais (Stable Diffusion)  
│     └── *.safetensors, *.ckpt  
├── diffusion_models/   ➔ Modelos de difusão alternativos  
│     └── *.safetensors, *.gguf  
├── loras/              ➔ Adaptadores de estilo (LoRAs)  
│     └── *.safetensors  
├── controlnet/         ➔ Controles de composição (ControlNets)  
│     └── *.pth, *.safetensors  
├── vae/                ➔ Decodificadores / Encoders do VAE  
│     └── *.pt, *.safetensors  
├── clip/               ➔ Encoders CLIP de texto  
│     └── *.safetensors, *.pt  
├── clip_vision/        ➔ Encoders CLIP de visão  
│     └── *.safetensors  
├── text_encoders/      ➔ Encoders de texto avançados  
│     └── *.safetensors, *.bin  
├── upscale_models/     ➔ Modelos de super‑resolução  
│     └── *.pth  
├── sams/               ➔ Modelos Segment Anything (SAM)  
│     └── *.pth, *.onnx  
├── style_models/       ➔ Modelos de estilo visual  
│     └── *.safetensors  
└── embeddings/         ➔ Embeddings / conceitos personalizados  
      └── *.pt, *.safetensors  </code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tem que baixar as coisas certas nos lugares certos. E pra dificultar, como já falei, os arquivos costumam ter a mesma extensão. Então é muito fácil confundir e jogar um text encoder no diretório de clip ou vice-versa. De novo, por isso encorajo que atualize o arquivo <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/models.conf"target="_blank" rel="noopener">&ldquo;models.conf&rdquo;</a> e coloque as URLs certas nas listas certas, assim fica bem mais organizado e o meu Docker vai puxar as coisas pros lugares certos quando inicializar.</p>
<p>Ainda assim pode ter algumas outras dúvidas que eu também tive no começo. Por exemplo <strong>&ldquo;Por que a U-NET é chamada de Checkpoint?&rdquo;</strong></p>
<h3><strong>Checkpoints ≈ Sistema Operacional Completo</strong><span class="hx:absolute hx:-mt-20" id="checkpoints--sistema-operacional-completo"></span>
    <a href="#checkpoints--sistema-operacional-completo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>
<p><strong>Semelhança</strong>: Assim como um commit no Git captura um estado específico do código, um checkpoint é um &ldquo;snapshot&rdquo; completo de um modelo treinado em um estágio específico.</p>
</li>
<li>
<p><strong>Diferença</strong>: Checkpoints são <strong>autocontidos</strong> (não são &ldquo;diferenças&rdquo; como commits), cada um é um modelo completo que pode funcionar independentemente.</p>
</li>
<li>
<p><strong>Melhor Metáfora</strong>: Pense em checkpoints como diferentes versões do Windows (XP, 7, 10) - cada um é um sistema operacional completo, não incremental.</p>
</li>
</ul>
<p>Então checkpoints meio que se assemelham, em conceito, a um &ldquo;git commit&rdquo; - se você for desenvolvedor. Ou snapshots de BTRFS ou ZFS - se você for de infra/devops.</p>
<h3><strong>LoRAs ≈ Plugins/Extensions</strong><span class="hx:absolute hx:-mt-20" id="loras--pluginsextensions"></span>
    <a href="#loras--pluginsextensions" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>
<p><strong>Semelhança</strong>: Assim como um PR adiciona funcionalidades a um código base, um LoRA modifica o comportamento do modelo base.</p>
</li>
<li>
<p><strong>Diferença</strong>: LoRAs não são &ldquo;merged&rdquo; no checkpoint original, mas sim <strong>aplicados dinamicamente</strong> durante a inferência.</p>
</li>
<li>
<p><strong>Melhor Metáfora</strong>: São como extensões do Chrome (ex: AdBlock) - adicionam comportamentos específicos sem modificar o navegador em si.</p>
</li>
</ul>
<p>Pense assim: um modelo tem Checkpoints, como se fossem commits, assim dá pra &ldquo;continuar&rdquo; o treino por cima se precisar. Ao mesmo tempo, não precisamos mexer no modelo principal, podemos criar camadas separadas, as chamadas LoRas. Por exemplo, digamos que um modelo esteja com dificuldades de gerar o seu rosto, não importe que prompt use ou que Nodes configure.</p>
<p>Em vez disso podemos usar o &ldquo;dicionário&rdquo; do modelo pré-treinado, como Flux ou SDXL e fazer um treinamento com nossas fotos pessoais e gerar um LoRa específico meu. Então podemos adicionar um Node de LoRa no workflow e isso vai resultar em imagens muito melhores com seu rosto em particular.</p>
<p>Existem comunidades online inteiras dedicadas a isso. Um exemplo. No site <a href="https://civitai.com/"target="_blank" rel="noopener">Civit A.I.</a> vamos encontrar diversos modelos baseados em modelos pré-existentes, como este <a href="https://civitai.com/models/24149/mistoonanime"target="_blank" rel="noopener">Mistoon</a> que é baseado no SDXL.</p>
<p><a href="https://civitai.com/models/24149/mistoonanime"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/a4s6zs7oq6rtwto79lqk5itgd4or" alt="Mistoon Civit.ai"  loading="lazy" /></a></p>
<p>É um checkpoint. Podemos nos cadastrar no site e e baixar o arquivo <code>mistoonAnime_v10Noobai.safetensor</code> e colocar no diretório <code>models/checkpoints</code>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/17z9fbwbzkg5kwoxzrcivpwwd74c" alt="Mistoon Checkpoint Node"  loading="lazy" /></p>
<p>Agora, em todo workflow que tiver o Node &ldquo;Load Checkpoint&rdquo; temos a opção de escolher esse modelo. Mas digamos que esse modelo não esteja conseguindo gerar uma personagem com uniforme colegial como em vários animes. Vasculhando o site, encontramos esta <strong>LoRa</strong>:</p>
<p><a href="https://civitai.com/models/115968/mistoonanime-school-uniform"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xlvhozw5u90xx9130a1kin5dl0da" alt="Mistoon LoRa"  loading="lazy" /></a>
O modelo Mistoon é grande, uns 6.6GB mas essa LoRa - chamada <code>Mistoon_Anime\ school\ uniform.safetensor</code> que devemos mover pra <code>models/loras</code> tem menos de 290 MB, é muito menor porque é um treinamento específico só em imagens de uniformes colegiais.</p>
<p>Agora podemos ligar o Node de &ldquo;Load Checkpoint&rdquo; a este outro Node de &ldquo;Load LoRa&rdquo; e escolher essa LoRa que sabemos que é compatível:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/8ahr2bgdxe5jg3xg4y2wcie3qzvs" alt="LoRa Node"  loading="lazy" /></p>
<p>LoRas tem que ser escolhidas pra usar com os modelos em que elas foram baseadas. Como falei antes, pense no modelo como uma &ldquo;língua&rdquo;, se fizermos um LoRa em alemão, não adianta misturar com um modelo chinês, a grosso modo. Mas essa é uma forma de fazer fine-tuning pro modelo que você gosta entregar resultados que nenhum outro consegue.</p>
<p>Um problema que eu tenho com o site Civit A.I. é que só dá pra fazer download quando está logado no site. Por isso não coloquei nenhum modelo ou lora deles no meu script de instalação automática. Infelizmente no caso deles precisa manualmente baixar os arquivos e mover pros diretórios certos, anotar em algum lugar, pra em outra máquina baixar e mover tudo de novo. É um saco que eu ainda não parei pra resolver. Se alguém quiser contribuir uma solução pra isso (sem expor cookies de login de ninguém, mandem Pull Request).</p>
<h3>Um Exemplo Real<span class="hx:absolute hx:-mt-20" id="um-exemplo-real"></span>
    <a href="#um-exemplo-real" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Seu dia a dia com Comfy, sendo um novato como eu vai ser mais ou menos assim: começa indo no Google e procurando &ldquo;best workflow image to anime ComfyUI&rdquo;. Primeiros links costumam ser alguma thread de Reddit:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/zhnvm94r6ze0279obqq9k0wd6t9c" alt="Reddit"  loading="lazy" /></p>
<p>Sub-Reddits como <a href="https://www.reddit.com/r/comfyui"target="_blank" rel="noopener">r/comfyui</a> ou <a href="https://www.reddit.com/r/StableDiffusion"target="_blank" rel="noopener">r/StableDiffusion</a> costumam ter novidades e workflows como esse. Daí vamos direto pros primeiros comentários:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/n3y2qd1vxw4uldxlrs5bjsewt9vn" alt="Reddit Comentários Links"  loading="lazy" /></p>
<p>Ou isso, ou abrimos o workflow direto no ComfyUI e vasculhamos os Nodes, um a um:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/b6hu6k4ygjpjdcq0chzv6mt1kx9w" alt="Nodes Check"  loading="lazy" /></p>
<p>Já temos talvez um problema: o Node veio pré-configurado pra carregar um LoRa chamado &ldquo;SDXL_aidma-niji_jini.safetensors&rdquo; mas o link no comentário do Reddit baixa um &ldquo;SDXL_Niji_V6_DLC_LoRa_V4.safetensors&rdquo;. Procurando especificamente pelo anterior no Google, eu não acho. Mas parece &ldquo;seguro&rdquo; usar esse outro, pelo menos os nomes são muito parecidos, ambos são derivados de SDXL então teoricamente são compatíveis pelo menos. Esta á a página desse modelo no Civit A.I., então tem que baixar manualmente e mandar pro diretório <code>models/loras</code> a mesma coisa pro Checkpoint AniToon no Node acima. Baixa manualmente e move pra <code>models/checkpoints</code>.</p>
<p><a href="https://civitai.com/models/541460/sdxlnijiv6dlclora"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/m8u6e9hc0vyrd41pdru0c26akolr" alt="LoRa Niji"  loading="lazy" /></a></p>
<p>Note que nessa página ele explica que esse LoRa foi feito pra funcionar com o modelo SDXL_Niji_V6 e não com o AniToon como o Workflow que baixei sugere no Node de Load Checkpoint. Vale testar com ambos. De qualquer forma, já que estamos no site, vamos baixar o modelo sugerido também:</p>
<p><a href="https://civitai.com/models/120765"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/zrkk8wr9v1z24s5f5fgyv01uui4g" alt="SDXL Niji"  loading="lazy" /></a></p>
<p>Arquivo <code>sdxlNijiSeven_sdxlNijiSeven.safetensors</code> de 6.5GB, move pra <code>models/checkpoints</code>, tão entendendo o processo? Baixa, move pro lugar certo, recarrega o workflow no navegador e agora aparece, selecionamos:</p>
<p>Aliás, acho que é intuitivo mas pra quem não entendeu se abriu a interface do ComfyUI:</p>
<ul>
<li>botão esquerdo do mouse seleciona Nodes</li>
<li>botão do meio arrasta a tela</li>
<li>scroll faz zoom na interface</li>
</ul>
<p>Agora sim, vamos escolher esses dois pra testar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/m7bzo57hfez23vazar5ct4j5kndq" alt="Nodes Niji"  loading="lazy" /></p>
<p>Aproveitando essa mesma imagem, note no canto esquerdo um Node de Florence2 que é uma LLM da Microsoft (e meu script de Docker já instala pra você). Não dá pra mostrar todos os Nodes relacionados mas estes são os principais de pra que isso serve:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/8pjx9pr8x0jsmz7bhouz5zcft1zt" alt="Florence"  loading="lazy" /></p>
<p>O ideal em geração de imagem é fazer o prompt mais longo e bem detalhado sobre a imagem original que você conseguir, mas a maioria das pessoas só escreve alguma porcaria como &ldquo;pessoa sorrindo de pé&rdquo; e acha que é suficiente. O modelo Florence2 é feito pra ler uma imagem e descrever ela em texto.</p>
<p>Agora tem outro detalhe: muitos checkpoints ou Loras são treinados com alguma palavra-chave de ativação. Assim ela só influencia se você digitar essas palavras no seu prompt. Então esse workflow não precisa digitar prompt manualmente, o Florence2 vai fazer isso e toda vez o texto dele começa como &ldquo;The image &hellip; bla bla bla&rdquo;, daí temos um Node de &ldquo;Text Find and Replace&rdquo; que troca esse &ldquo;The Image&rdquo; pelas palavras chaves do Anitoon ou Niji: &ldquo;aidma-niji, niji&rdquo;.</p>
<p>Essas são as palavras certas? <strong>EU NÃO SEI</strong> kkkkk</p>
<p>O workflow já trouxe essas palavras, mas nas páginas do Civit A.I. - onde deveria estar documentado, não está!! Então estamos realmente <strong>CHUTANDO</strong> que deve ser &ldquo;niji&rdquo; já que é o nome do modelo e do lora. Preste atenção na palavra-chave, é importante, senão você adiciona o lora e nada acontece de diferente e é porque faltou isso no prompt!!</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4i1n0fc3uvvy85nr17x6imee33bh" alt="ControlNet"  loading="lazy" /></p>
<p>Tem mais, tem que checar se os Nodes de coisas como VAE, ControlNet, Canny e tudo mais estão populados com arquivos que temos. Normalmente se usa a mesma meia dúzia e eu já pré-instalei no Docker os principais. Na imagem, veja que ControlNet é o <code>contronet-union-sdxl-1.0-promax.safetensors</code>, compatível com SDXL e estamos usando Niji que é feito em cima de SDXL. Vamos chutando assim.</p>
<p>Também notem que ele usa Zoe Depth Map pra tirar o Mapa de Profundidade e Canny pra tirar o Mapa de Bordas como no outro workflow. Dá pra configurar, nesse workflow note que o Mapa de Bordas pegou alguns artefatos esquisitos no fundo, dá pra ajustar pra tirar isso. Por isso que no exemplo anterior também a imagem final apareceu uma janela, o modelo achou que era uma janela! Mas por enquanto deixa assim mesmo.</p>
<p>Agora podemos rodar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/29nzuo5t1nr6mn4wss0t77at94no" alt="Run Comfy"  loading="lazy" /></p>
<p>Tem um botão enorme &ldquo;RUN&rdquo; em azul lá embaixo. Se estiver tudo configurado certo, você vai ver que um trabalho foi colocado na fila. A interface é inteligente o suficiente pra deixar você trabalhar em outros workflow e só ir enfileirando trabalhos enquanto a GPU sua processando. Não precisa ficar esperando, só deixar vários enfileirados e ir dormir.</p>
<p>Ao rodar, na interface os Nodes que estão processando neste momento vão ficar com uma borda verde, o que é bom pra você saber em que etapa do processo ele está. E se algum Node tiver erro - porque faltou arquivo, por exemplo - ele vai ficar com borda vermelha, daí precisa corrigir e rodar de novo.</p>
<p>E no final, eis um dos resultados:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/7mbzamhj8c5sc2fesp8kkz6cvjhu" alt="Resultado Niji"  loading="lazy" /></p>
<p>Note como os mapas da controlnet garantiram a pose correta. O modelo deduziu aqueles artefatos de fundo do mapa de borda como iluminação de alguma janela, mas na foto original vemos que é tipo o tijolo de concreto da parede kkkk mas é isso, sem saber, ele chuta. E se rodar várias vezes, sempre vai dar um resultado bem diferente. Olha outro:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xbxu131ivlqu4endtdf6j5ayxzxt" alt="Niji 2"  loading="lazy" /></p>
<p>Nada a ver kkkk Mas podemos trocar o modelo SDXL-Niji pelo Anitoon. Vamos ver um dos resultados:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/w7fsjrui29pbp8e98ka4ubuvtl6f" alt="Anitoon"  loading="lazy" /></p>
<p>Trocando pro modelo waiNSFIllustrious:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2un0xtv7g210xrqc74nbceiwg7t4" alt="waiNSFIllustrious"  loading="lazy" /></p>
<p>Esse ficou ainda mais nada a ver. Alguma coisa nos mapas está fazendo o modelo assumir que eu tenho cabelo cacheado, sei lá porque. Mas é isso: tem que ajustar os parâmetros. O resultado do workflow que mostrei no começo do artigo deu um resultado melhor, usando os mesmos modelos.</p>
<p>De todos os Nodes, um dos mais importante é o &ldquo;motor&rdquo; do processo, o <strong>KSampler</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/n6875915bhqq39vxw0kfripyyj3d" alt="KSampler"  loading="lazy" /></p>
<p>Esse é o cara que comanda o show. Damos pra ele o modelo, o prompt, saídas de control-net e ele quem vai iterativamente (steps) fazer o &ldquo;desnoise&rdquo; do Latente. Note que sempre existe um fator aleatório (&ldquo;seed&rdquo;), quantidade de passos (25 a 50 é a média), &ldquo;cfg&rdquo; (classifier-free guidance) ou &ldquo;guidance_scale&rdquo;, onde valores maiores puxam a imagem mais seu prompt positivo. Denoise é quando de noise o scheduler aplica (1.0 é noise completo, abaixo de 1.0 pode produzir resultados mais &ldquo;artísticos&rdquo;, tem que testar).</p>
<p>Sampler Name é um saco porque precisaria entender a matemática por trás, mas são os algoritmos de steps, por exemplo <code>euler</code>, <code>ddim</code>, <code>plms</code> e cada um tem um trade-off entre velocidade e fidelidade. Na prática: teste um a um e veja os resultados. Scheduler tem <code>normal</code>, <code>klms</code>, <code>dpmsolver</code>. Mesma coisa: teste um a um.</p>
<p>Como podem ver, tem BASTANTE parâmetros que dá pra brincar. Mas o fluxo principal é mais ou menos esse. Daí tem que ir aprendendo Node a Node o que cada um pode fazer e baixar bastante workflow pré-pronto pra ver como o pessoal tem solucionado diversos problema, quais os modelos e loras mais populares pra cada tipo de aplicação. Enfim, o Reddit é seu amigo. Espero que tenha dado pra dar um Norte, eu também ainda estou aprendendo.</p>
]]></content:encoded><category>midia-generativa</category><category>machine-learning</category><category>tutoriais</category></item><item><title>Gerando Imagens com I.A - até estilo Ghibli 😂 - com Docker e CUDA</title><link>https://akitaonrails.github.io/2025/04/20/gerando-imagens-com-i-a-ate-estilo-ghibli-com-docker-e-cuda/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/20/gerando-imagens-com-i-a-ate-estilo-ghibli-com-docker-e-cuda/</guid><pubDate>Sun, 20 Apr 2025 17:30:00 -0300</pubDate><description>&lt;p&gt;Este tema vai ser dividido em dois posts. Neste é só técnico de como fazer rodar, no outro vou explicar o que diabos é um ComfyUI e mais ou menos como usar.&lt;/p&gt;
&lt;p&gt;Mais de ano atrás, quando fiz meus últimos videos sobre I.A. e nos podcasts eu sempre falava que rodava tudo na minha máquina. Mas nunca detahei como. Então hoje consertei isso com vários posts de blog e projetinhos no GitHub com Dockerfiles pra você mesmo rodar na sua própria máquina, sem configurar nada difícil.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este tema vai ser dividido em dois posts. Neste é só técnico de como fazer rodar, no outro vou explicar o que diabos é um ComfyUI e mais ou menos como usar.</p>
<p>Mais de ano atrás, quando fiz meus últimos videos sobre I.A. e nos podcasts eu sempre falava que rodava tudo na minha máquina. Mas nunca detahei como. Então hoje consertei isso com vários posts de blog e projetinhos no GitHub com Dockerfiles pra você mesmo rodar na sua própria máquina, sem configurar nada difícil.</p>
<p>Finalmente fiz um <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded"target="_blank" rel="noopener">Dockerfile</a> pra ComfyUI, que é a melhor interface gráfica pra edição de workflows de geração de imagens. Se você já trabalhou com Davinci Resolve Fusion ou Blender Geometry Nodes da vida, workflows são basicamente configurações de nós pra processar suas imagens, áudio, video e ir passando por processamentos não só de I.A.. É como programar visualmente. Por isso é uma ferramenta muito mais complicada do que o FramePack que mostrei no post anterior. Não é só clicar.</p>
<p>Recentemente todo mundo ficou histérico com as atualizações do ChatGPT e como ele <em>&ldquo;OH MY GOD&rdquo;</em> consegue gerar imagens em estilo de anime da Ghibli e ficaram spammando o mundo com memes idiotas em estilo anime. Histeria online é um saco mesmo. Mas como eu falei, por mais impressionante que seja, é isso: um gerador de memes. Não dá pra usar profissionalmente.</p>
<p>Cansei de tentar subir fotos minhas ou até fotos genéricas, de personagens fictícios, alguns até desenhos meus, e vira e mexe, aleatoriamente, o ChatGPT se recusa a gerar a imagem. Alegando privacidade, segurança, copyright e tudo mais que ele conseguir inventar de desculpa. Não dá pra confiar nisso pra trabalhar. Quem é profissional deveria ter controle completo sobre o processo e é isso que vou ensinar hoje.</p>
<p>Pra começar, clone meu <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded"target="_blank" rel="noopener">repositório no GitHub</a> e siga as instruções do README ou faça isto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git clone https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded.git
cd ComfyUI-Docker-CUDA-preloaded
docker compose build</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Sendo mais complexo, precisei colocar um Docker Compose desta vez. Então pra subir, depois do build finalizar, só fazer isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>docker compose up</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora vai demorar, porque por padrão ele vai baixar quase <strong>500GB</strong> de modelos pré-treinados pra conseguir fazer quase tudo de imagens. Sim, pra brincar com I.A. localmente precisa ter infra. Ou se não, alugar máquina parruda com muito armazenamento numa RunPod da vida e rodar remoto. Tem como baixar menos modelos, vou explicar a seguir.</p>
<p>No Docker Compose. Recomendo evitar a opção &ldquo;-d&rdquo; de subir como daemon, porque é bom reservar um terminal pra ficar vendo os outputs (você pode dar <code>docker compose logs --follow</code>, se quiser também, mas é mais fácil direto).</p>
<p>O ComfyUI é um programa mais complexo que a média, muito mais que um FramePack da vida. Ele tem suporte a instalar &ldquo;extensions&rdquo; (plugins, addons, chame como quiser). E na verdade ele sozinho não faz muita coisa, precisa instalar várias extensions pra começar a ficar realmente útil. Vira e mexe alguma dessas extensions não trata erros direito e estoura no console, algo como isso:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/n7w5oxun51elrsv2fra46f684xre" alt="Console Error"  loading="lazy" /></p>
<p>É útil sempre ficar de olho no console pra descobrir se ele tentou carregar um arquivo que não existe. Daí dá pra pegar o nome do arquivo e buscar no Google, baixar e colocar no lugar certo. Ou se é uma extension muito bugada que é melhor tirar do diretório &ldquo;custom_nodes&rdquo;, e assim por diante. Sem ver o console, não aparece na interface web e você vai ficar a ver navios.</p>
<p>Muito dos modelos e extensions eu me baseei na configuraçãol <strong>Ultimate ComfyUI versão 3</strong> recomendada pelo <strong>Aitrepreneur</strong>, de novo. Assista <a href="https://youtu.be/q5kpr84uyzc"target="_blank" rel="noopener">este video dele</a> pra ter todos os detalhes e tudo que é possível fazer. Ele tem os scripts pra Linux, Windows, e os workflow na versão paga do Patreon dele e, claro, não vou compartilhar os workflows porque não são meus. Em teoria, minha config deve ser capaz de fazer o que ele ensina.</p>
<p>O workflow V3 do Aitrepreneur é muito ph0da. Mas eu não posso compartilhar porque é material fechado no Patreon dele, mas eis uma foto de tela do meu ComfyUI com tudo dele carregado:</p>
<p><a href="https://www.patreon.com/posts/ultimate-all-in-121355089"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/1p3xfs0ecfimbw4yg55qz17buti6" alt="Ultimate V3"  loading="lazy" /></a></p>
<p>Mas a config em si, eu refiz inteira pra funcionar neste Docker. Dá pra carregar qualquer outro workflow aberto que achar num Reddit da vida.</p>
<p>O que eu fiz foi o seguinte:</p>
<h2>Models<span class="hx:absolute hx:-mt-20" id="models"></span>
    <a href="#models" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No projeto tem que ter um diretório &ldquo;models&rdquo;, se não tiver crie, porque o docker compose vai mapear pra dentro do container. Quando iniciar, ele vai rodar o arquivo <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/init_models.sh"target="_blank" rel="noopener">init_models.sh</a>. Esse script vai carregar um arquivo de configuração que você pode editar depois: <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/models.conf"target="_blank" rel="noopener">models.conf</a>. Ele é enorme e um pedaço dele é assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
[CONTROLNET]
https://huggingface.co/XLabs-AI/flux-controlnet-collections/resolve/main/flux-canny-controlnet-v3.safetensors
https://huggingface.co/XLabs-AI/flux-controlnet-collections/resolve/main/flux-depth-controlnet-v3.safetensors
https://huggingface.co/brad-twinkl/controlnet-union-sdxl-1.0-promax/resolve/main/diffusion_pytorch_model.safetensors|controlnet-union-sdxl-1.0-promax.safetensors
https://huggingface.co/lllyasviel/ControlNet-v1-1/resolve/main/control_v11p_sd15_canny.pth
https://huggingface.co/lllyasviel/ControlNet-v1-1/resolve/main/control_v11f1p_sd15_depth.pth
https://huggingface.co/lllyasviel/ControlNet-v1-1/resolve/main/control_v11p_sd15_openpose.pth
https://huggingface.co/Aitrepreneur/FLX/resolve/main/Shakker-LabsFLUX1-dev-ControlNet-Union-Pro.safetensors

[STYLE_MODELS]
https://huggingface.co/Aitrepreneur/FLX/resolve/main/flux1-redux-dev.safetensors

[SAMS]
https://huggingface.co/Aitrepreneur/FLX/resolve/main/sam_vit_b_01ec64.pth</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Cada seção mapeia pra um sub-diretório, então &ldquo;[SAMS]&rdquo; vai mapear pra &ldquo;models/sams&rdquo; e baixar os binários nos lugares certos. Tem alguns especiais nas seções &ldquo;[GIT_REPOS]&rdquo;, para os que podem ser clonados via git ou &ldquo;[CUSTOM]&rdquo; pra arquivos que precisa baixar depois dentro desses projetos clonados, por exemplo. Não sei ainda se dá pra simplificar, mas isso eu peguei na config do Aitrepreneur.</p>
<p>O ideal é sempre manter essa lista organizada com modelos que acharem depois. Se quiserem contribuir também seria bacana. O motivo é que fica tudo espalhado em tudo que é site por aí, você baixa manualmente, daí move pro diretório dentro de &ldquo;models&rdquo;. Amanhã, se reinstalar sua máquina, e pra lembrar todos os sites? São dezenas de modelos disponíveis.</p>
<p>No outro post vou tentar resumir o que diabos são &ldquo;checkpoints&rdquo;, &ldquo;diffusion models&rdquo;, &ldquo;loras&rdquo;, &ldquo;controlnet&rdquo; e tudo mais. Mas por enquanto, a extensão dos arquivos não diz muita coisa. Dizer &ldquo;.pt&rdquo; por exemplo só quer dizer que é um arquivo que o PyTorch consegue abrir, mas não diz se é um clip ou um text encoder, por exemplo. Extensão &ldquo;.safetensors&rdquo; mesma coisa, pode ser qualquer coisa, e o ComfyUI precisa que cada arquivo esteja no sub-diretório correto pra conseguir usar depois, não pode sair movendo arquivo aleatoriamente.</p>
<p>O script &ldquo;init_models.sh&rdquo; roda toda vez que o Docker é reiniciado e é inteligente pra só baixar URLs que já não estão baixadas no diretório &ldquo;models&rdquo;. Por isso sempre roda quando o container sobe, no entrypoint. Essa é uma das vantagens da minha config: ela é tem manutenção automática.</p>
<h3>Extensions e Dependências<span class="hx:absolute hx:-mt-20" id="extensions-e-dependências"></span>
    <a href="#extensions-e-depend%c3%aancias" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu fiz um segundo script chamado <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/init_extensions.sh"target="_blank" rel="noopener">init_extensions.sh</a>, que também roda no entrypoint do container e você não deve mexer, mas ele carrega um outro arquivo de configuração chamado <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/extensions.conf"target="_blank" rel="noopener">extensions.conf</a> e, esse sim, você pode mexer e adicionar novas extensions depois. Um pedaço dele é assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[EXTENSIONS]
https://github.com/ltdrdata/ComfyUI-Manager.git
https://codeberg.org/Gourieff/comfyui-reactor-node.git
https://github.com/ssitu/ComfyUI_UltimateSDUpscale.git
https://github.com/kijai/ComfyUI-LivePortraitKJ.git
https://github.com/PowerHouseMan/ComfyUI-AdvancedLivePortrait.git
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Aqui eu declaro todas as extensions que adicionam novas funcionalidades em cima do ComfyUI, em particular novos <strong>NODES</strong>, por isso ficam no diretório &ldquo;custom_nodes&rdquo;, que é um volume externo ao Docker e por isso é persistente, ou seja, toda nova extension sobrevive restarts do container.</p>
<p>Mas tem ainda um porém GRANDE: <strong>Dependências de Python</strong>. Um pesadelo.</p>
<p>A build da Imagem Docker faz um monte de <code>pip install</code> e como estou usando Virtual ENV, vai tudo instalado em <code>/venv</code> dentro do container, como deveria.</p>
<p>Porém, as extensions que são instaladas fora da build - toda vez que o container reinicia - costumam ser código Python e também tem mais dependências. Extensions são projetos no GitHub e podem estar mudando neste exato momento. Se mudar e fizer um <code>git pull</code> pra atualizar, precisa rodar o <code>pip install -r requirements.txt</code>. Mas se tiver dezenas de extensions, rodar <code>pip install</code>, de cada um, em cada restart do Docker é uma demora grande pra não fazer quase nada. Como eu disse, pesadelo.</p>
<p>Minha solução - pra me gabar um pouco - conserta boa parte dessa manutenção. Quando o script <code>init_extensions.sh</code> executa, se uma extension da lista já estiver instalada, ele vai pular. Daí vai gravar no diretório <code>./custom_nodes/.last_commit/*.commit</code> o último commit de cada extension. Quando o Docker reiniciar, ele vai dar um <code>git fetch</code> e checar se o hash do último commit mudou.</p>
<p>Só se mudou, ele vai rodar <code>pip install</code> pra essa extension que ganhou atualização e pular todas as outras que não mudaram desde o último restart, economizando MUITO tempo e evitando que você precisa manualmente atualizar as coisas. Como estou usando VENV, vai ficar tudo persistido no volume &ldquo;/venv&rdquo;. Sim, é um VOLUME, porque ela muda depois do build a cada atualização de extension.</p>
<p>Dessa forma, tudo que tem na imagem vai ser copiada nesse volume, e tudo que as extensions instalarem por fora, também vai pra dentro desse volume. Toda vez que reiniciar, volta tudo como estava antes. Foi a solução que encontrei e até agora parece funcionar direitinho.</p>
<p>Mas é só questão de tempo pra alguma extension mal feita instalar dependências que quebrem outras dependências, ou um caso real que eu passei: tinha uma extension que crasheia sem mensagem de erro se estiver usando Python 3.11, o Zoe Depth Map da extension <code>comfyui_controlnet_aux</code>, um dos pacotes que parece que são populares.</p>
<p>Eu não tinha me dado conta que comecei usando uma image base de Docker de ubuntu22 em vez de ubuntu24, eu estava com medo de usar algo novo demais e quebrar, mas acabei usando velha demais e quebrou do mesmo jeito. Gerenciar versionamento é um saco. Enfim, mudei o Dockerfile pra ubuntu24.</p>
<p>Mas no volume externo do VENV agora sobrou um monte de dependências que se limitavam ao Python 3.11. Não tem problema, basta apagar o volume:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>docker compose down # garantir que está parado
docker volume rm comfyui-venv
rm ./custom_nodes/.last_commits/*.commit</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E pronto, o VENV vai reiniciar vazio, preencher com as dependências corretas de Python 3.12 da imagem e ao iniciar o container meu script <code>init_extensions.sh</code> não vai achar o &ldquo;.last_commits&rdquo; e vai forçar um <code>pip install -r requirements.txt</code> de todas as extensions, agora com Python 3.12. Super limpo e confiável.</p>
<p>Se mesmo assim tiver erro, aí pode ser bug numa extension em especial, só vá até o diretório dele em &ldquo;./custom_nodes&rdquo; e apague manualmente antes de reiniciar o container.</p>
<p>Acho que isso é o menos complicado que dá pra fazer nesse caso. Esse docker não foi feito pra rodar sozinho num servidor, é pra rodar na sua própria máquina local, onde você está monitorando o output do terminal mesmo.</p>
<p>Ah sim, se sair versão nova do ComfyUI aí tem que re-buildar a imagem e apagar o volume de VENV. A imagem é estática, não esqueçam. Dentro o Dockerfile faz um novo git clone e pega o commit mais novo. Daí vai instalar pacotes python mais novos, então precisa zerar o volume da VENV e fazer as extensions re-instalarem suas dependências depois também. É um enorme saco, mas é assim mesmo que funciona.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Feito tudo isso, com um simples <code>docker compose up</code> consigo ter um ambiente de ComfyUI estável e isolado do resto do meu sistema, sem sujar nada com milhares de dependências duvidosas. Quando terminar só fazer <code>docker compose down</code> ou &ldquo;ctrl-c&rdquo; no console dele e pronto, meu sistema continua limpo.</p>
<p>Além disso, diretórios como &ldquo;models&rdquo; e seus 500GB de modelos ficam separados, dá pra fazer backup pra outro lugar se precisar. E se quiser subir em outra máquina, basta levar somente esses diretórios e subir Docker Compose lá e tem que subir igualzinho, zero-setup. Esse foi meu objetivo ao Dockerzar ComfyUI, FramePack, Video2K, ter tudo portátil, isolado e limpo pra subir em qualquer máquina depois sem precisar ficar rodando scripts duvidosos de instalação direto na minha máquina.</p>
<p><a href="https://x.com/AkitaOnRails/status/1914030815962411367"target="_blank" rel="noopener">No meu X</a> eu já mostrei meu ComfyUI funcionando. Carreguei um workflow qualquer que achei na Web e olha só:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jhx6v5va7qac4vocp17ku5o6ha51" alt="ComfyUI"  loading="lazy" /></p>
<p>Nesse workflow eu subo uma imagem de referência e posso escrever um prompt de como quero transformar ela:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20xq72nojtvb887mebeksr6q32cw" alt="Prompt"  loading="lazy" /></p>
<p>Com ComfyUI eu tenho controle sobre todas as etapas do processo, por exemplo, esse workflow consegue separar coisas como Mapa de Profundidade e Contorno de Bordas da imagem original, pra usar de contexto antes de fazer a imagem final, garantindo um resultado muito mais preciso:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4r1en3a557znzmy0zojk8ei9tflu" alt="Depth Map"  loading="lazy" /></p>
<p>E eis um resultado que já consegui usando o modelo &ldquo;waiNSFIllustrious&rdquo; que esse workflow sugere, que é um modelo treinado pra gerar imagens estilo anime (tem dezenas, pra vários estilos diferentes).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/m7amy8gmp5y4snztatjjlwlr0wye" alt="anime"  loading="lazy" /></p>
<p>Mudando o modelo pra <code>Mistoon</code>, baseado em Stable Diffusion (SDXL), eis outro resultado, com o mesmo prompt, no mesmo workflow:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/lkj86wdlvh5fy8h78a8ml5io8t6g" alt="anime 2"  loading="lazy" /></p>
<p>E posso ficar tunando dezenas de parâmetros dentro desse workflow, trocar vaes, text encodes, controlnets e muito mais até conseguir exatamente o resultado que eu quiser. Esse é o poder do ComfyUI e seus Custom Nodes e Workflows. É o que vou tentar explicar o básico no próximo post.</p>
]]></content:encoded><category>midia-generativa</category><category>containers</category><category>tutoriais</category></item><item><title>Gerando Videos de até 2 min a partir de uma Foto com I.A.</title><link>https://akitaonrails.github.io/2025/04/19/gerando-videos-de-ate-2-min-a-partir-de-uma-foto-com-i-a/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/19/gerando-videos-de-ate-2-min-a-partir-de-uma-foto-com-i-a/</guid><pubDate>Sat, 19 Apr 2025 20:00:00 -0300</pubDate><description>&lt;p&gt;Fácil o melhor canal de tutoriais de ferramentas open source pra I.A. é o &lt;a href="https://www.youtube.com/watch?v=GywyMij88rY"target="_blank" rel="noopener"&gt;Aitrepreneur&lt;/a&gt;. E nesse link ele apresenta uma nova ferramenta chamada &lt;a href="https://github.com/lllyasviel/FramePack"target="_blank" rel="noopener"&gt;FramePack&lt;/a&gt;, que usa o famoso modelo &lt;a href="https://github.com/Tencent/HunyuanVideo"target="_blank" rel="noopener"&gt;HunyuanVideo&lt;/a&gt; da Tencent pra pegar uma única imagem, uma foto, e conseguir gerar videos de excelente qualidade de até 2 minutos de duração. É realmente impressionante! Assistam o video, mas eis aqui embaixo um video gerado de 10 segundos de uma foto que eu tirei de uma das action figures da minha coleção.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Fácil o melhor canal de tutoriais de ferramentas open source pra I.A. é o <a href="https://www.youtube.com/watch?v=GywyMij88rY"target="_blank" rel="noopener">Aitrepreneur</a>. E nesse link ele apresenta uma nova ferramenta chamada <a href="https://github.com/lllyasviel/FramePack"target="_blank" rel="noopener">FramePack</a>, que usa o famoso modelo <a href="https://github.com/Tencent/HunyuanVideo"target="_blank" rel="noopener">HunyuanVideo</a> da Tencent pra pegar uma única imagem, uma foto, e conseguir gerar videos de excelente qualidade de até 2 minutos de duração. É realmente impressionante! Assistam o video, mas eis aqui embaixo um video gerado de 10 segundos de uma foto que eu tirei de uma das action figures da minha coleção.</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/link-animado.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/link-animado.mp4)
    </video>
</div>
<p>Já que estava no embalo de empacotar esse tipo de ferramenta em Docker, resolvi fazer mais um projetinho com um Dockerfile pra rodar no meu Manjaro Linux com RTX 4090. O bom do Aitrepreneur é que ele tem um Patreon que você pode assinar pra contribuir (é barato) e ele sempre sobe scripts pra rodar no <a href="https://www.runpod.io/"target="_blank" rel="noopener">RunPod</a> caso você não tenha uma máquina parruda ou GPU, ou script .BAT pra rodar no seu Windows.</p>
<p>Meu projetinho está <a href="https://github.com/akitaonrails/FramePack-Docker-CUDA"target="_blank" rel="noopener">neste GitHub</a> e pra usar é muito simples:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>
git clone https://github.com/akitaonrails/FramePack-Docker-CUDA.git
cd FramePack-Docker-CUDA
mkdir outputs
mkdir hf_download

# Build the image
docker build -t framepack-torch26-cu124:latest .

# Run mapping the directories outside:
docker run -it --rm --gpus all -p 7860:7860 \
  -v ./outputs:/app/outputs \
  -v ./hf_download:/app/hf_download \
  framepack-torch26-cu124:latest</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso vai construir a imagem de Docker e rodar. E na primeira vez dá pra ver que ele vai baixar uma TONELADA de models, o HunyuanVideo, Flux e mais. Se prepara pra mais de 30GB. Mas é só na primeira vez porque eu mapeio o diretório de download pra fora, então se reiniciar o container ele já vai ter na próxima vez.</p>
<p>Quando terminar, é só acessar <strong>http://localhost:7860</strong> e vai ver esta interface super simples:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/x5nwxu25vlb9ox5kda5xiz6b4fwf" alt="FramePack Web UI"  loading="lazy" /></p>
<p>Os controles são muito simples:</p>
<ul>
<li>Desabilite a opção &ldquo;Use TeaCache&rdquo; que deixa mais rápido mas aí você arrisca a ter aqueles defeitos de I.A. como mão com mais dedos e coisas assim.</li>
<li>Total do Video pode ser até 2 minutos, mas só faça isso se realmente tiver paciência porque demora BASTANTE! Acho melhor começar mesmo testando com videos curtos de 5 a 10 segundos primeiro.</li>
<li>Se sua GPU tiver pouca VRAM vai ser obrigado a mexer em &ldquo;GPU Inference Preserved Memory (GB) (larger means slower)&rdquo;. No meu caso que tenho 24GB de VRAM deixei como está, mas olha só rodando como ele consome:</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/5purckhhytuqd7bwp2wjqhyxq66p" alt="btop"  loading="lazy" /></p>
<p>Sim! 100% da GPU e quase 100% da VRAM, puxando mais de 350W da parede! (foda-se meio ambiente! kkkkk) A CPU até fica de boa, porque esse processo é feito pra realmente MASSACRAR a GPU. Se não tiver GPU boa, alugue uma máquina na RunPod como falei antes.</p>
<p>O video de 10 segundos no começo do post é uma foto da uma das action figures que eu mais gosto na minha coleção, o Link de Breath of the Wild. E isso é uma coisa que me deixou empolgado. Esta é a foto original que serviu de base pro FramePack fazer o video:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/g3dm9u1ij8r6gl0dk90qool0htyw" alt="Link Foto"  loading="lazy" /></p>
<p>Fazer video de fotos de pessoas é sem graça e tedioso, mas agora que estou aprendendo a modelar 3D pra imprimir na minha Bambulab, imagina depois ainda conseguir fazer ele ficar animado?</p>
<p>Pra animar no Blender e renderizar, também é bastante processamento e demora. Mas com isso eu consigo rapidamente prototipar uma animação e ver como fica, pra ver se vale a pena fazer uma versão melhor no Blender depois. Abre muitas possibildades de experimentação!</p>
<p>Por mais impressionante que isso pareça, ele não faz <strong>QUALQUER COISA</strong>, tive várias experimentações que deram errado e a movimentação que ele faz é bem limitada, afinal ele só tem uma foto pra usar de referência e não tem dá pra fazer movimentos bruscos demais, tem limites. Mesmo assim é bem divertido de brincar. O fato dele conseguir identificar mais que seres humanos é um grande &ldquo;plus&rdquo; na minha opinião, porque é onde eu me interessei mais.</p>
<p><strong>AH SIM!</strong> Lembram o <a href="https://www.akitaonrails.com/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a"target="_blank" rel="noopener">outro blog post</a> que publiquei hoje sobre fazer <strong>UPSCALE PRA 4K</strong>?? Dá pra usar aqui também, pegue um video que o FramePack gerou e você gostou muito e faça upscale pra 4K pra ter mais definição ainda! Aí fica em qualidade pra até usar em edição de videos.</p>
<p>Videos de 2 minutos deve levar horas pra fazer. 30 segundos levou quase 1 hora. Melhor coisa é ficar em videos de 5 segundos pra brincar e experimentar, só isso já leva aí quase meia hora. Aqui vão mais alguns exemplos tirados de fotos de itens da minha coleção (aqueles que vocês viam no fundo dos meus videos). Divirtam-se!</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/ultraseven-animado.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/ultraseven-animado.mp4)
    </video>
</div>
<p>Este é um Ultraseven de mais de 30cm de altura que eu encontrei e trouxe direto de um sebo de Tóquio ano passado, um dos que eu mais gosto especialmente porque assisti muito no começo dos anos 80 quando eu era criança.</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/jiraya-animado.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/jiraya-animado.mp4)
    </video>
</div>
<p>Falando em nostalgia, esse é outro item da hora da minha coleção, um Jiraya super detalhado feito pela Iron Studios. E animado até que ficou bem da hora. O que acharam?</p>
<p>Mas agora o meu <strong>FAVORITO</strong>, quem me acompanha no <a href="https://www.instagram.com/p/DIAe3pNOFgffFZ5J32fWlc-8Kmy3Lhap5Vm58U0/?img_index=1"target="_blank" rel="noopener">Instagram</a> viu que faz algumas semanas que venho me dedicando a melhorar minhas técnicas de 3D e também de desenho e eu fiz um conceito de Mandaloriam + Judge Dredd:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/01qz4daelmm671m5ytgua036373h" alt="Boba Dredd"  loading="lazy" />.png?disposition=attachment&amp;locale=en)</p>
<p>Depois vejam os outros desenhos no meu Instagram, mas enfim, eu precisava saber se o FramePack era capaz de animar também desenhos e SIM, ELE CONSEGUE!! OLHA QUE PH0DA!!! Esse me deixou impressionado, dá uma nova dimensão nas minhas coisas!</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/boba-dredd-animado_upscaled.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/boba-dredd-animado_upscaled.mp4)
    </video>
</div>
<p>Sim, tudo isso é bem pesado, mas a vantagem é que eu posso experimentar INFINITAMENTE, porque tudo roda local. Num produto comercial de alguém no &ldquo;cloud&rdquo;, eu precisaria pagar alguma assinatura e ele ia me limitar, seria impossível ficar experimentando quando eu quisesse. Agora eu posso fazer tudo que eu quiser, quando eu quiser, como eu quiser, sem que ninguém nem nada possa encher meu s@co.</p>
<p>Aliás, se puderem assistam esse clip do &ldquo;Boba Dredd&rdquo; em tela cheia num monitor grande. Notaram que tá bem nítido? É porque eu passei no Video2K e fiz upscale pra 4x a resolução que o FramePack me deu. Juntando essas ferramentas dá pra fazer coisas BEM interessantes, depois me falem se conseguiram usar e o que fizeram de legal!</p>
]]></content:encoded><category>midia-generativa</category><category>containers</category></item><item><title>Aumentando Resolução de Anime velho pra 4K com I.A.</title><link>https://akitaonrails.github.io/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a/</guid><pubDate>Sat, 19 Apr 2025 15:30:00 -0300</pubDate><description>&lt;p&gt;Mais de um ano atrás eu estava brincando com ErsGAN, redes adversariais generativas pra tarefa de fazer &amp;ldquo;upscaling&amp;rdquo; (aumentar resolução) de arquivos de animes velhos que eu tenho.&lt;/p&gt;
&lt;p&gt;Pra quem coleciona, o problema é que muito anime dos anos 90 pra trás nunca saíram e nem nunca vão sair em Blu-Ray (1080p) nem UHD (4K). Só os mais famosos recebem tratamento de &amp;ldquo;remaster&amp;rdquo; (pegar as fitas master originais e recapturar em mais resolução). Então muito anime velho está preso na era de DVD (480p) ou VHS (480i).&lt;/p&gt;</description><content:encoded><![CDATA[<p>Mais de um ano atrás eu estava brincando com ErsGAN, redes adversariais generativas pra tarefa de fazer &ldquo;upscaling&rdquo; (aumentar resolução) de arquivos de animes velhos que eu tenho.</p>
<p>Pra quem coleciona, o problema é que muito anime dos anos 90 pra trás nunca saíram e nem nunca vão sair em Blu-Ray (1080p) nem UHD (4K). Só os mais famosos recebem tratamento de &ldquo;remaster&rdquo; (pegar as fitas master originais e recapturar em mais resolução). Então muito anime velho está preso na era de DVD (480p) ou VHS (480i).</p>
<p>Nem todo anime velho dá pra aumentar resolução, pode ficar &ldquo;lavado&rdquo; demais. Quem baixa torrent já deve ter visto upscalings mal feitos. Mas eu queria poder testar isso eu mesmo, na minha máquina local, com meus arquivos velhos. Eis um pequeno clip de exemplo:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip1.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip1.mp4)
    </video>
</div>
<p>É um pedaço do 1o episódio do lendário Rurouni Kenshin de 1996. Sim, hoje temos um remake, que estão refazendo do zero. Mas pra quem tem nostalgia pelo original, não tem substituição. A qualidade de DVD não está horrenda, dá pra assistir de boa ainda hoje assim mesmo. Mas fico curioso pra ver como fica em 4K.</p>
<p>Não fiz nenhuma pesquisa longa, mas um ano atrás eu tinha esbarrado no <a href="https://github.com/xinntao/Real-ESRGAN"target="_blank" rel="noopener"><strong>Real ERSGAN</strong></a>. Esse projeto está descontinuado faz uns 4 anos, então não sei o que é o mais moderno hoje. Mas os modelos pré-treinados deles ainda são úteis.</p>
<p>Ainda tem projetos que usam esse modelo, o que eu conheço é o <a href="https://github.com/k4yt3x/video2x"target="_blank" rel="noopener"><strong>Video2K</strong></a> Em Windows parece que tem até uma interface gráfica bonitinha, mas a vantagem pra Linux é que ele roda em Docker. Um exemplo seria assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>docker run --rm --gpus all \
  -v &#34;$PWD/videos_in&#34;:/input \
  -v &#34;$PWD/videos_out&#34;:/output \
  ghcr.io/k4yt3x/video2x:6.4.0 \
  -i /input/old_anime.mp4 -o /output/old_anime_4K.mkv \
  -p realesrgan -s 4 --realesrgan-model realesr-animevideov3</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se tiver com sua NVIDIA configurada com o Container Toolkit, Cuda e tudo mais (pergunte ao ChatGPT), o Docker consegue mapear o dispositivo direto pra dentro do container. Isso facilita muito não deixar meu Linux sujo cheio de dependências e configuraçõe soltas por aí. Não faço mais nada desse tipo fora de containers.</p>
<p>Mas eu esbarrei com um problema. Se você estiver usando GPU AMD, só assim já deve funcionar, porque essa imagem Docker na verdade usa Vulkan e não CUDA. No meu caso eu tenho uma GPU integrada AMD primária e uma NVIDIA RTX 4090 secundária. Eu tentei passar direto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ docker run --gpus all --privileged \
  --device=/dev/nvidia0 \
  --device=/dev/nvidiactl \
  --device=/dev/nvidia-uvm \
  --device=/dev/nvidia-modeset \
  --runtime=nvidia \
  -e VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json \
  -e VK_LAYER_PATH=/usr/share/vulkan/explicit_layer.d \
  -v &#34;$HOME/Downloads/Video2K/videos_in&#34;:/input \
  -v &#34;$HOME/Downloads/Video2K/videos_out&#34;:/output \
  --rm -it \
  ghcr.io/k4yt3x/video2x:6.4.0 \
  -i &#34;/input/(B-A)Rurouni_Kenshin_-_01_(A5D3CB57).mkv&#34; \
  -o &#34;/output/(B-A)Rurouni_Kenshin_-_01_(A5D3CB57)_upscaled.mkv&#34; \
  -p realesrgan -s 4 --realesrgan-model realesr-animevideov3</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mas não vai de jeito nenhum. Se eu só disser &ldquo;&ndash;gpus all&rdquo; ele vai pegar a iGPU por padrão. Fazer o upscaling de um video acaba consumindo 100% da minha CPU, todas as 32 threads, mas minha GPU NVIDIA fica parada olhando sem fazer nada. Se eu tentar passar &ldquo;/dev/dri/card0&rdquo; ou &ldquo;/dev/nvidia0&rdquo;, ele dá erro e não consegue começar.</p>
<p>Então a partir daqui a solução é só pra quem tem NVIDIA:</p>
<h3>Dockerfile CUDA<span class="hx:absolute hx:-mt-20" id="dockerfile-cuda"></span>
    <a href="#dockerfile-cuda" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Desta vez resolvi não ser preguiçoso e organizar tudo no projetinho no Github. Clone direto de lá <a href="https://github.com/akitaonrails/Batch-Anime-Upscaler-Video2K-Docker-CUDA"target="_blank" rel="noopener">Batch Anime Upscaler Video2K Docker CUDA</a> Só fazer assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git clone git@github.com:akitaonrails/Batch-Anime-Upscaler-Video2K-Docker-CUDA.git
cd Batch-Anime-Upscaler-Video2K-Docker-CUDA
mkdir input # coloque seus videos velhos aqui
mkdir output

docker build --build-arg HOST_UID=$(id -u) --build-arg HOST_GID=$(id -g) -t anime-upscaler:latest .</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se nada der errado, isso vai construir uma imagem de Docker com tudo que você precisa, daí é colocar seus videos no subdiretório que falei, ou outro que quiser e configurar no comando pra rodar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code> docker run --gpus all --rm \
  -v &#34;./input&#34;:/input \
  -v &#34;./output&#34;:/output \
  -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) \
  anime-upscaler:latest</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é só isso. Demora bastante. Com meu Ryzen 7950X3D mais RTX 4090, ele processa a uma taxa de uns 13 frames por segundo. Então menos da metade da velocidade de playback. Se um video tiver 20 min, vai levar mais de 40 minutos pra processar cada um. É bom reservar um bom tempo, e por isso também é bom só pra videos que você realmente queira assistir.</p>
<p>O script original do Video2K suporta vários parâmetros, que eu deixei expostos como variáveis de ambiente no Dockerfile:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>MODEL=&#34;${MODEL:-realesr-animevideov3}&#34;
SCALE=&#34;${SCALE:-4}&#34;
TILE=&#34;${TILE:-0}&#34;
DENOISE=&#34;${DENOISE:-1.0}&#34;
NUM_PROC=&#34;${NUM_PROC:-1}&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Daí dá pra mudar mexendo assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>docker run --gpus all --rm \
  -e DENOISE=0.5 \
  -v ... \
  anime-upscaler:latest</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como podem ver, por padrão usamos o modelo pré-treinado &ldquo;realesr-animevideov3&rdquo; que é menos detalhado que o modelo de 6 bilhões de parâmetro mas é bem mais estável entre frames, que é mais importante pra um video. A maioria dos outros modelos que ele suporta, como &ldquo;RealESRGAN_x4plus&rdquo; ou &ldquo;ESRGAN_x4&rdquo; servem pra fotos e imagens mas não pra video. Video exige coerência temporal, que é bem difícil.</p>
<p>No máximo vale testar o modelo &ldquo;realesr-general-x4v3&rdquo; que dizem ser um pouco mais leve, caso sua máquina esteja capengando muito pra rodar o padrão. Mas aí vai ter que modificar o Dockerfile pra fazer download desse modelo e re-buildar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># === OPTIONAL: Preload additional Real-ESRGAN models ===
# Uncomment any models you want available in the container at build time

# Anime 6-block model (better for anime stills, line art)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRGAN_x4plus_anime_6B.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRGAN_x4plus_anime_6B.pth

# Original RealESRGAN 4x general-purpose model
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRGAN_x4plus.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRGAN_x4plus.pth

# RealESRGAN 2x model (lighter upscale)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRGAN_x2plus.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRGAN_x2plus.pth

# ESRNet (non-GAN model, ultra-smooth)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRNet_x4plus.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRNet_x4plus.pth

# General-purpose lightweight video-friendly model (with denoise control)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.3.0/realesr-general-x4v3.pth \
#     -O /opt/Real-ESRGAN/weights/realesr-general-x4v3.pth

# Anime video model (default in your setup)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-animevideov3.pth \
#     -O /opt/Real-ESRGAN/weights/realesr-animevideov3.pth

# Original ESRGAN (2018) model
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/ESRGAN_x4.pth \
#     -O /opt/Real-ESRGAN/weights/ESRGAN_x4.pth</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Eu não testei todos, mas pra quem quiser, acho que esses são os links. Mude no Dockerfile, faça o build, mude a variável MODEL e teste nos seus videos.</p>
<p>O que vale mexer são coisas como DENOISE, que está pra &ldquo;1.0&rdquo; que é máximo, mas isso pode deixar a imagem &ldquo;esticada&rdquo; demais. Vale testar valores menores, como &ldquo;0.5&rdquo;. Varia de caso a caso, só testando.</p>
<p>Outro fator é a escala, SCALE, que o default é &ldquo;4x&rdquo;, que transforma 1080p em 4K ou 720p em 1440p. Mas às vezes não precisa subir tanto, e nem adianta porque a imagem original já tem muito pouco detalhe mesmo. Talvez só 2x seja suficiente. O script suporta 2x, 3x, 4x mas tem teto na resoluçãod e 4K, então não adianta tentar transformar video que já é 4K em 8K por exemplo.</p>
<p>Minha RTX 4090 e meu Ryzen 9 são bem potentes. Processando só um video eu não vejo ele suando nem um pouco:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2dz1abq9b1wteho3r1onowmij14v" alt="btop"  loading="lazy" /></p>
<p>Claramente daria pra rodar mais videos em paralelo. Tem que tomar cuidado quando é desbalanceado. Se a CPU subir pra perto de 100% pra separar os frames, não adianta estar sobrando na GPU. Os dois tendo sobra, dá pra fazer o script subir mais de 1 processo na GPU e mandar frames em paralelo com a opção &ldquo;-e NUM_PROC=2&rdquo;. Olha como agora ele já usa mais da GPU e a CPU ainda tá sobrando. Talvez 3 seja o máximo, mas tem que testar, não é constante o tempo todo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qur7lw4ox5s3nymh025u6q446831" alt="btop x2"  loading="lazy" /></p>
<p>Isso pode diminuir o tempo total dramaticamente. Você pode escolher entre aumentar os processo na GPU pelo script ou ter dois diretórios separados de video e subir dois containers paralelos desse programa pra rodar ao mesmo tempo, o que funcionar melhor pra você.</p>
<h2>Resultado Final<span class="hx:absolute hx:-mt-20" id="resultado-final"></span>
    <a href="#resultado-final" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Vamos ver como ficou aquele primeiro clip com upscale 4x e denoise 1.0:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip1_upscaled_jp.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip1_upscaled_jp.mp4)
    </video>
</div>
<p>Dá uma diferença ENORME, mas se ficou &ldquo;BOM&rdquo; vai ser BEM subjetivo. Eu particularmente não achava o original ruim de assistir. Talvez se mexer no DENOISE pra um pouco menos ou SCALE pra 2x em vez de 4x já fique bom, não sei.</p>
<p>Pra comparar, eis outro clip na versão original antiga:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip2.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip2.mp4)
    </video>
</div>
<p>E de novo, como fica depois do upscale 4x:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip2_upscaled_jp.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip2_upscaled_jp.mp4)
    </video>
</div>
<p>Pelo menos está funcionando como deveria, a partir daqui é fazer algum tuning. Se alguém tiver mais dicas ou conhecer modelos ou projetos mais novos de upscaling open source, mande nos comentários abaixo!</p>
<p>Antes que alguém pergunte, porque não usar DLSS 3 da NVIDIA, que eles usam pra aumentar resolução de games? Resposta: porque eles não expõe essa funcionalidade como uma API que dá pra acessar, é só pros drivers proprietários deles em profile de jogo. Eles ligam e tunam caso a caso. Não tem API geral na lib CUDA.</p>
<h2>Update pós-publicação<span class="hx:absolute hx:-mt-20" id="update-pós-publicação"></span>
    <a href="#update-p%c3%b3s-publica%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Resolvi testar o modelo RealERSGAN_x4plus_anime_6B, que é bem mais pesado que o realesr-animevideov3 que subi no repositório.</p>
<p>Mude o Dockerfile e adicione isto depois do wget do outro modelo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth \
     -O /opt/Real-ESRGAN/weights/RealESRGAN_x4plus_anime_6B.pth</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse modelo não tem como rodar em paralelo nem na minha RTX 4090, que fica constantemente em 100%. Se antes ia a uns 13 frames por segundo, com esse vai só a 3 frames por segundo. É 4x mais pesado e somente pra GPUs realmente parrudas. Se não tiver algo assim, fique na &ldquo;realesr-animevideov3&rdquo; mesmo ou no mais leve ainda (menos qualidade) &ldquo;realesr-general-x4v3&rdquo;. Esses modelos dependem muito da potência da GPU e principalmente, quantidade de VRAM.</p>
<p>Pra rodar adicione &ldquo;-e MODEL=RealESRGAN_x4plus_anime_6B&rdquo; no comando &ldquo;docker run&rdquo;.</p>
<p>Veja como ficou o mesmo clip de antes usando este modelo:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_upscaled_clip1_x4plus.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_upscaled_clip1_x4plus.mp4)
    </video>
</div>
<p>Pra ter gasto 4x mais recursos da minha máquina pra processar, o resultado final eu não achei que ficou tão diferente, e em algumas partes eu acho que ficou mais defeitos até. Então é isso, o resultado vai variar, tem que testar. Depende muito da qualidade do anime original, mas não é porque existe um modelo maior e teoricamente &ldquo;melhor&rdquo; que todo resultado vai ficar melhor.</p>
]]></content:encoded><category>midia-generativa</category><category>performance</category><category>containers</category></item><item><title>Colorindo Imagens Preto e Branco com I.A.</title><link>https://akitaonrails.github.io/2025/04/19/colorindo-imagens-preto-e-branco-com-i-a/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/19/colorindo-imagens-preto-e-branco-com-i-a/</guid><pubDate>Sat, 19 Apr 2025 01:40:00 -0300</pubDate><description>&lt;p&gt;Minha namorada me deu um desafio hoje: ela tinha fotos preto e branco antigas e queria saber se eu conseguia colorizar elas. Se sair procurando na Web esbarra em alguns sites pra isso, como esse: &lt;a href="https://palette.fm/"target="_blank" rel="noopener"&gt;Palette&lt;/a&gt;. Mas é pago, não é barato e acho que ele não consegue uma coisa que ela queria: usar uma outra imagem colorida como referência pra tirar as cores em vez de tentar colorizar por chute do modelo.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Minha namorada me deu um desafio hoje: ela tinha fotos preto e branco antigas e queria saber se eu conseguia colorizar elas. Se sair procurando na Web esbarra em alguns sites pra isso, como esse: <a href="https://palette.fm/"target="_blank" rel="noopener">Palette</a>. Mas é pago, não é barato e acho que ele não consegue uma coisa que ela queria: usar uma outra imagem colorida como referência pra tirar as cores em vez de tentar colorizar por chute do modelo.</p>
<p>Saí fuçando GitHub e tem uma página <a href="https://github.com/MarkMoHR/Awesome-Image-Colorization"target="_blank" rel="noopener">Awesome Image Colorization</a> com vários papers de pesquisa. Muito útil pra quem for pesquisador mas totalmente inútil pra mim que não vou fazer um do zero kkkk. Tinha links pra alguns projetos como ChromaGAN, mas que está descontinuado faz uns 5 anos. E vários outros projetos que eu vi foram descontinuados lá atrás mesmo, não sei porque tem tão pouco open source ainda atualizado.</p>
<p>Mas felizmente esbarrei em um promissor: <a href="https://github.com/piddnad/DDColor"target="_blank" rel="noopener">DDColor</a>.</p>
<p>Bora clonar o repositório. E pra continuar, prefiro fazer tudo dentro de Docker. Ficar baixando dependência de python sempre zoa meu sistema e deixa um monte de lixo pra trás, então melhor coisa é isolar tudo. Outra coisa, precisa baixar os binários dos modelos pré-treinados. Só pra isso vou usar um pouco de Python por causa da lib &ldquo;modelscope&rdquo; que faz isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/piddnad/DDColor.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> DDColor
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">mkdir modelscope
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># criar um novo venv dentro do projeto DDColor</span>
</span></span><span class="line"><span class="cl">python -m venv venv
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">venv/bin/pip3 install modelscope
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">venv/bin/python3 -c <span class="s2">&#34;from modelscope.hub.snapshot_download import snapshot_download; snapshot_download(&#39;damo/cv_ddcolor_image-colorization&#39;, cache_dir=&#39;./modelscope&#39;)&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Boa prática de Python acho que é fazer tudo dentro de uma VENV né? Enfim, isso deve baixar o binário do modelo, que pesa quase 900MB. Não é grande.</p>
<p>Agora precisamos de um Dockerfile:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Use NVIDIA CUDA base image with cuDNN 8 and Python support
FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04

# Install Python and system dependencies
RUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends \
    python3 python3-pip git libgl1 libglib2.0-0 &amp;&amp; \
    apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /app

# Copy DDColor source code into the image
COPY . /app

# Install PyTorch (CUDA 11.8 compatible) and matching torchvision/torchaudio
RUN pip3 install torch==2.2.0 torchvision==0.17.0 torchaudio==2.2.0 \
    --index-url https://download.pytorch.org/whl/cu118

# Install Python dependencies, skipping dlib (not needed for inference)
RUN sed &#39;/dlib/d&#39; requirements.txt &gt; temp-req.txt &amp;&amp; pip3 install -r temp-req.txt &amp;&amp; rm temp-req.txt

# Optional: install modelscope if you want to run snapshot_download manually inside the container
RUN pip3 install modelscope</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Preguiça de abrir um pull request pra isso, se alguém quiser, mande lá.</p>
<p>Agora precisamos colocar uma imagem preto e branco em algum lugar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mkdir input
mkdir results
mv ~/Downloads/bw.jpg input/</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como as fotos da minha namorada são familiares e particulares, obviamente não vou compartilhar, então peguei uma aleatória qualquer no Google Images:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qgt3lynkp1f5uzkbysyvio2kmq04" alt="Foto B&W"  loading="lazy" /></p>
<p>Agora colocamos em &ldquo;./input&rdquo; e rodamos o Docker com este comando:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>docker run --rm --gpus all \
  -v &#34;$PWD/input&#34;:/workspace/input_images:ro \
  -v &#34;$PWD/results&#34;:/workspace/results:rw \
  -v &#34;$PWD/modelscope&#34;:/app/modelscope:ro \
  ddcolor:latest \
  python3 infer.py \
    --model_path /app/modelscope/damo/cv_ddcolor_image-colorization/pytorch_model.pt \
    --input /workspace/input_images \
    --output /workspace/results</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Preste atenção no mapeamento de diretórios. E note a opção &ldquo;&ndash;gpus&rdquo; que só funciona no meu caso porque eu tenho uma RTX 4090 instalada aqui. Não tenho a mínima idéia se é a mesma coisa com AMD, mas como a imagem é baseada em cnDNN/CUDA, acho que não. Pergunte ao ChatGPT.</p>
<p>Se estiver tudo certo e rodar com sucesso, vai levar só 1 segundo e ter este output no terminal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>==========
== CUDA ==
==========

CUDA Version 11.8.0

Container image Copyright (c) 2016-2023, NVIDIA CORPORATION &amp; AFFILIATES. All rights reserved.

This container image and its contents are governed by the NVIDIA Deep Learning Container License.
By pulling and using the container, you accept the terms and conditions of this license:
https://developer.nvidia.com/ngc/nvidia-deep-learning-container-license

A copy of this license is made available in this container at /NGC-DL-CONTAINER-LICENSE for your convenience.

Output path: /workspace/results
100%|██████████| 1/1 [00:00&lt;00:00,  4.09it/s]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E eis o resultado que aparece no diretório `./results&quot;:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/o2r085pzzpsavbyr39btg1vq0hwp" alt="Foto re-colorizada"  loading="lazy" /></p>
<p>Sim, eu fiquei impressionado. Mesmo a imagem sendo da Web, com marca d&rsquo;agua e tudo, ele fez tudo direitinho e funcionou!!</p>
<p>Único problema é que ainda falta o problema de conseguir usar uma imagem de referência pra tentar pegar as cores. Esse é um problema mais difícil e eu perguntei ao ChatGPT que me respondeu isto:</p>
<p><strong>Sem suporte nativo para imagem de referência:</strong></p>
<p>O modelo DDColor, tanto na versão oficial hospedada no ModelScope quanto no repositório GitHub <code>piddnad/DDColor</code>, foi projetado apenas para colorização <strong>automática de imagens em preto e branco</strong>, sem aceitar uma imagem de referência como entrada. A própria descrição oficial deixa claro que o modelo recebe <strong>uma única imagem P&amp;B</strong> e retorna uma imagem colorida.</p>
<p>O método <code>forward</code> do modelo espera <strong>apenas um tensor</strong>, referente à imagem P&amp;B convertida em RGB artificial (na verdade, a luminância expandida para 3 canais). Ou seja, <strong>não há lugar no modelo para passar uma segunda imagem como referência de cores</strong>.</p>
<p>Isso também fica evidente no <code>predict.py</code> original da biblioteca Cog, que aceita apenas o parâmetro <code>image</code> como entrada — sem suporte a <code>reference</code>, <code>ref_image</code> ou algo do tipo.</p>
<h3>Como resolver o problema: duas abordagens possíveis<span class="hx:absolute hx:-mt-20" id="como-resolver-o-problema-duas-abordagens-possíveis"></span>
    <a href="#como-resolver-o-problema-duas-abordagens-poss%c3%adveis" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como o DDColor não suporta nativamente uma imagem de referência para guiar as cores, temos duas alternativas:</p>
<h3>🔁 1. Usar um modelo especializado em colorização com referência<span class="hx:absolute hx:-mt-20" id="-1-usar-um-modelo-especializado-em-colorização-com-referência"></span>
    <a href="#-1-usar-um-modelo-especializado-em-coloriza%c3%a7%c3%a3o-com-refer%c3%aancia" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Existem modelos desenvolvidos especificamente para isso, como:</p>
<ul>
<li>
<p><strong>Deep Exemplar-based Colorization</strong> (Zhang et al., CVPR 2018)</p>
</li>
<li>
<p><strong>ChromaGAN</strong> (2019)</p>
</li>
<li>
<p><strong>Exemplar-Transformer Colorization</strong> (Yin et al., ACM MM 2021)</p>
</li>
<li>
<p><strong>UniColor</strong> (Huang et al., 2022)</p>
</li>
</ul>
<p>Esses modelos aceitam <strong>duas imagens</strong>:</p>
<ul>
<li>
<p>A imagem em P&amp;B (entrada principal)</p>
</li>
<li>
<p>Uma imagem colorida como <strong>referência de cores</strong></p>
</li>
</ul>
<p>Esses modelos mapeiam similaridades semânticas (ex: “pele aqui deve ter a mesma cor de pele dali”) e fazem uma transferência <strong>baseada no conteúdo da imagem</strong>, obtendo resultados muito mais detalhados e específicos. No entanto, isso exigiria:</p>
<ul>
<li>
<p>Baixar pesos diferentes</p>
</li>
<li>
<p>Alterar completamente a arquitetura usada</p>
</li>
<li>
<p>Adaptar o pipeline e a inferência</p>
</li>
</ul>
<h3>🎯 2. Usar transferência global de cor após a saída do DDColor (nossa abordagem atual)<span class="hx:absolute hx:-mt-20" id="-2-usar-transferência-global-de-cor-após-a-saída-do-ddcolor-nossa-abordagem-atual"></span>
    <a href="#-2-usar-transfer%c3%aancia-global-de-cor-ap%c3%b3s-a-sa%c3%adda-do-ddcolor-nossa-abordagem-atual" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A solução prática e leve que usamos foi:<br>
➡️ Rodar o DDColor normalmente para gerar a imagem colorida automaticamente<br>
➡️ Depois, ajustar <strong>globalmente</strong> as cores do resultado para ficarem mais parecidas com as da imagem de referência.</p>
<p>Esse ajuste é feito com o algoritmo de <strong>Reinhard et al. (2001)</strong>:</p>
<ol>
<li>
<p>Convertemos tanto a imagem de saída quanto a referência para o espaço de cor Lab.</p>
</li>
<li>
<p>Calculamos a <strong>média e o desvio padrão</strong> dos canais <code>a</code> e <code>b</code> (os que representam cor).</p>
</li>
<li>
<p>Ajustamos o resultado para ter <strong>a mesma distribuição estatística</strong> de cor que a imagem de referência.</p>
</li>
<li>
<p>Isso tende a deixar a imagem com o mesmo “clima” ou tom geral (mais quente, mais frio, mais vibrante).</p>
</li>
</ol>
<p>Esse método é rápido, leve, e funciona <strong>sem alterar o modelo DDColor</strong>.</p>
<h2>Continuando<span class="hx:absolute hx:-mt-20" id="continuando"></span>
    <a href="#continuando" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu usei a modificação que ele sugeriu ao arquivo predict.py:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">cv2</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">torch</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">torch.nn.functional</span> <span class="k">as</span> <span class="nn">F</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">cog</span> <span class="kn">import</span> <span class="n">BasePredictor</span><span class="p">,</span> <span class="n">Input</span><span class="p">,</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">basicsr.archs.ddcolor_arch</span> <span class="kn">import</span> <span class="n">DDColor</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">color_transfer</span><span class="p">(</span><span class="n">reference_bgr</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">ndarray</span><span class="p">,</span> <span class="n">target_bgr</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">ndarray</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">np</span><span class="o">.</span><span class="n">ndarray</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Transfer color palette of reference image to target image using Lab mean/std.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Convert BGR images to Lab color space (float32 for precision)</span>
</span></span><span class="line"><span class="cl">    <span class="n">ref_lab</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">reference_bgr</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_BGR2LAB</span><span class="p">)</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;float32&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">tgt_lab</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">target_bgr</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_BGR2LAB</span><span class="p">)</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;float32&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Split channels</span>
</span></span><span class="line"><span class="cl">    <span class="n">L_ref</span><span class="p">,</span> <span class="n">a_ref</span><span class="p">,</span> <span class="n">b_ref</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">ref_lab</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">L_tgt</span><span class="p">,</span> <span class="n">a_tgt</span><span class="p">,</span> <span class="n">b_tgt</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">tgt_lab</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Compute mean and std for reference and target (a and b channels)</span>
</span></span><span class="line"><span class="cl">    <span class="n">a_ref_mean</span><span class="p">,</span> <span class="n">a_ref_std</span> <span class="o">=</span> <span class="n">a_ref</span><span class="o">.</span><span class="n">mean</span><span class="p">(),</span> <span class="n">a_ref</span><span class="o">.</span><span class="n">std</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">b_ref_mean</span><span class="p">,</span> <span class="n">b_ref_std</span> <span class="o">=</span> <span class="n">b_ref</span><span class="o">.</span><span class="n">mean</span><span class="p">(),</span> <span class="n">b_ref</span><span class="o">.</span><span class="n">std</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">a_tgt_mean</span><span class="p">,</span> <span class="n">a_tgt_std</span> <span class="o">=</span> <span class="n">a_tgt</span><span class="o">.</span><span class="n">mean</span><span class="p">(),</span> <span class="n">a_tgt</span><span class="o">.</span><span class="n">std</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">b_tgt_mean</span><span class="p">,</span> <span class="n">b_tgt_std</span> <span class="o">=</span> <span class="n">b_tgt</span><span class="o">.</span><span class="n">mean</span><span class="p">(),</span> <span class="n">b_tgt</span><span class="o">.</span><span class="n">std</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Subtract target means, scale by reference/target std, add reference means</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># (Avoid division by zero in case of zero std)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">a_tgt_std</span> <span class="o">&gt;</span> <span class="mf">1e-6</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">a_tgt</span> <span class="o">=</span> <span class="p">((</span><span class="n">a_tgt</span> <span class="o">-</span> <span class="n">a_tgt_mean</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">a_ref_std</span> <span class="o">/</span> <span class="n">a_tgt_std</span><span class="p">))</span> <span class="o">+</span> <span class="n">a_ref_mean</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">a_tgt</span> <span class="o">=</span> <span class="n">a_tgt</span> <span class="o">-</span> <span class="n">a_tgt_mean</span> <span class="o">+</span> <span class="n">a_ref_mean</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">b_tgt_std</span> <span class="o">&gt;</span> <span class="mf">1e-6</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">b_tgt</span> <span class="o">=</span> <span class="p">((</span><span class="n">b_tgt</span> <span class="o">-</span> <span class="n">b_tgt_mean</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">b_ref_std</span> <span class="o">/</span> <span class="n">b_tgt_std</span><span class="p">))</span> <span class="o">+</span> <span class="n">b_ref_mean</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">b_tgt</span> <span class="o">=</span> <span class="n">b_tgt</span> <span class="o">-</span> <span class="n">b_tgt_mean</span> <span class="o">+</span> <span class="n">b_ref_mean</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Clip values to valid Lab range [0,255] after transfer</span>
</span></span><span class="line"><span class="cl">    <span class="n">a_tgt</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">clip</span><span class="p">(</span><span class="n">a_tgt</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">b_tgt</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">clip</span><span class="p">(</span><span class="n">b_tgt</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Merge channels back (use original L from target to preserve brightness)</span>
</span></span><span class="line"><span class="cl">    <span class="n">merged_lab</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">merge</span><span class="p">([</span><span class="n">L_tgt</span><span class="p">,</span> <span class="n">a_tgt</span><span class="p">,</span> <span class="n">b_tgt</span><span class="p">])</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;uint8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Convert back to BGR color space</span>
</span></span><span class="line"><span class="cl">    <span class="n">result_bgr</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">merged_lab</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_LAB2BGR</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">result_bgr</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ImageColorizationPipeline</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Helper pipeline to load DDColor model and process images.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">model_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">input_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">256</span><span class="p">,</span> <span class="n">model_size</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;large&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">device</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">device</span><span class="p">(</span><span class="s2">&#34;cuda&#34;</span> <span class="k">if</span> <span class="n">torch</span><span class="o">.</span><span class="n">cuda</span><span class="o">.</span><span class="n">is_available</span><span class="p">()</span> <span class="k">else</span> <span class="s2">&#34;cpu&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Choose encoder backbone based on model size</span>
</span></span><span class="line"><span class="cl">        <span class="n">encoder_name</span> <span class="o">=</span> <span class="s2">&#34;convnext-t&#34;</span> <span class="k">if</span> <span class="n">model_size</span> <span class="o">==</span> <span class="s2">&#34;tiny&#34;</span> <span class="k">else</span> <span class="s2">&#34;convnext-l&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Initialize DDColor model</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">model</span> <span class="o">=</span> <span class="n">DDColor</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">encoder_name</span><span class="o">=</span><span class="n">encoder_name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">decoder_name</span><span class="o">=</span><span class="s2">&#34;MultiScaleColorDecoder&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">input_size</span><span class="o">=</span><span class="p">[</span><span class="n">input_size</span><span class="p">,</span> <span class="n">input_size</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="n">num_output_channels</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span>    <span class="c1"># model predicts ab channels</span>
</span></span><span class="line"><span class="cl">            <span class="n">last_norm</span><span class="o">=</span><span class="s2">&#34;Spectral&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">do_normalize</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">num_queries</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">num_scales</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">dec_layers</span><span class="o">=</span><span class="mi">9</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span><span class="o">.</span><span class="n">to</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">device</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Load weights</span>
</span></span><span class="line"><span class="cl">        <span class="n">state</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">model_path</span><span class="p">,</span> <span class="n">map_location</span><span class="o">=</span><span class="s2">&#34;cpu&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Some checkpoints store weights under &#39;params&#39; key</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="o">.</span><span class="n">load_state_dict</span><span class="p">(</span><span class="n">state</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;params&#34;</span><span class="p">,</span> <span class="n">state</span><span class="p">),</span> <span class="n">strict</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="o">.</span><span class="n">eval</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">input_size</span> <span class="o">=</span> <span class="n">input_size</span>  <span class="c1"># store for processing</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@torch.no_grad</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">img_bgr</span><span class="p">:</span> <span class="n">np</span><span class="o">.</span><span class="n">ndarray</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">np</span><span class="o">.</span><span class="n">ndarray</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Colorize a BGR image (numpy array) using the loaded DDColor model.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Preserve original resolution L channel</span>
</span></span><span class="line"><span class="cl">        <span class="n">orig_h</span><span class="p">,</span> <span class="n">orig_w</span> <span class="o">=</span> <span class="n">img_bgr</span><span class="o">.</span><span class="n">shape</span><span class="p">[:</span><span class="mi">2</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Convert to Lab and extract L channel at original size</span>
</span></span><span class="line"><span class="cl">        <span class="n">img_lab</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">img_bgr</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_BGR2LAB</span><span class="p">)</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;float32&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">orig_L</span> <span class="o">=</span> <span class="n">img_lab</span><span class="p">[:,</span> <span class="p">:,</span> <span class="mi">0</span><span class="p">:</span><span class="mi">1</span><span class="p">]</span>  <span class="c1"># shape (H, W, 1)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Prepare grayscale input at model resolution</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Resize input to model expected size</span>
</span></span><span class="line"><span class="cl">        <span class="n">inp</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">resize</span><span class="p">(</span><span class="n">img_bgr</span><span class="p">,</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">input_size</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">input_size</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">inp_lab</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">inp</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_BGR2LAB</span><span class="p">)</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;float32&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">L</span> <span class="o">=</span> <span class="n">inp_lab</span><span class="p">[:,</span> <span class="p">:,</span> <span class="mi">0</span><span class="p">:</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Create grayscale Lab image by zeroing AB channels</span>
</span></span><span class="line"><span class="cl">        <span class="n">gray_lab</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">concatenate</span><span class="p">([</span><span class="n">L</span><span class="p">,</span> <span class="n">np</span><span class="o">.</span><span class="n">zeros_like</span><span class="p">(</span><span class="n">L</span><span class="p">),</span> <span class="n">np</span><span class="o">.</span><span class="n">zeros_like</span><span class="p">(</span><span class="n">L</span><span class="p">)],</span> <span class="n">axis</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Convert back to RGB (now it&#39;s a gray RGB image of size input_size)</span>
</span></span><span class="line"><span class="cl">        <span class="n">gray_rgb</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">gray_lab</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;uint8&#34;</span><span class="p">),</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_LAB2RGB</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Prepare tensor and run model</span>
</span></span><span class="line"><span class="cl">        <span class="n">tensor</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">from_numpy</span><span class="p">(</span><span class="n">gray_rgb</span><span class="o">.</span><span class="n">transpose</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span><span class="o">.</span><span class="n">float</span><span class="p">()</span><span class="o">.</span><span class="n">unsqueeze</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">.</span><span class="n">to</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">device</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_ab</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="p">(</span><span class="n">tensor</span><span class="p">)</span>  <span class="c1"># output is (1,2,H_out,W_out) in Lab AB</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_ab</span> <span class="o">=</span> <span class="n">out_ab</span><span class="o">.</span><span class="n">cpu</span><span class="p">()</span><span class="o">.</span><span class="n">float</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Resize output AB to original image size</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_ab_resized</span> <span class="o">=</span> <span class="n">F</span><span class="o">.</span><span class="n">interpolate</span><span class="p">(</span><span class="n">out_ab</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="p">(</span><span class="n">orig_h</span><span class="p">,</span> <span class="n">orig_w</span><span class="p">),</span> <span class="n">mode</span><span class="o">=</span><span class="s2">&#34;bilinear&#34;</span><span class="p">,</span> <span class="n">align_corners</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_ab_resized</span> <span class="o">=</span> <span class="n">out_ab_resized</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">numpy</span><span class="p">()</span><span class="o">.</span><span class="n">transpose</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>  <span class="c1"># (H, W, 2)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Combine original L and predicted AB, then convert to BGR</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_lab</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">concatenate</span><span class="p">([</span><span class="n">orig_L</span><span class="p">,</span> <span class="n">out_ab_resized</span><span class="p">],</span> <span class="n">axis</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">astype</span><span class="p">(</span><span class="s2">&#34;uint8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_bgr</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">out_lab</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">COLOR_LAB2BGR</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">out_bgr</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Predictor</span><span class="p">(</span><span class="n">BasePredictor</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">setup</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Load models into memory for efficient multiple predictions.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Determine paths for large and tiny model weights</span>
</span></span><span class="line"><span class="cl">        <span class="n">large_model_path</span> <span class="o">=</span> <span class="s2">&#34;checkpoints/ddcolor_modelscope.pth&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">tiny_model_path</span>  <span class="o">=</span> <span class="s2">&#34;checkpoints/ddcolor_paper_tiny.pth&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># (Ensure the above files exist. If not, download from ModelScope or HuggingFace as described.)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Initialize pipelines for large and tiny models</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">colorizer_large</span> <span class="o">=</span> <span class="n">ImageColorizationPipeline</span><span class="p">(</span><span class="n">model_path</span><span class="o">=</span><span class="n">large_model_path</span><span class="p">,</span> <span class="n">input_size</span><span class="o">=</span><span class="mi">512</span><span class="p">,</span> <span class="n">model_size</span><span class="o">=</span><span class="s2">&#34;large&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">colorizer_tiny</span>  <span class="o">=</span> <span class="n">ImageColorizationPipeline</span><span class="p">(</span><span class="n">model_path</span><span class="o">=</span><span class="n">tiny_model_path</span><span class="p">,</span>  <span class="n">input_size</span><span class="o">=</span><span class="mi">512</span><span class="p">,</span> <span class="n">model_size</span><span class="o">=</span><span class="s2">&#34;tiny&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">predict</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">image</span><span class="p">:</span> <span class="n">Path</span> <span class="o">=</span> <span class="n">Input</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&#34;Grayscale input image.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">reference</span><span class="p">:</span> <span class="n">Path</span> <span class="o">=</span> <span class="n">Input</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&#34;Optional reference image to guide colors&#34;</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">model_size</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Input</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&#34;Model size to use (large = highest quality, tiny = faster).&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                <span class="n">choices</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;large&#34;</span><span class="p">,</span> <span class="s2">&#34;tiny&#34;</span><span class="p">],</span> <span class="n">default</span><span class="o">=</span><span class="s2">&#34;large&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Path</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Run a single prediction. Colorizes the image, optionally using a reference image for color bias.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Read input image</span>
</span></span><span class="line"><span class="cl">        <span class="n">img</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">image</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">img</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;Failed to load input image&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Select model pipeline</span>
</span></span><span class="line"><span class="cl">        <span class="n">pipeline</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">colorizer_tiny</span> <span class="k">if</span> <span class="n">model_size</span> <span class="o">==</span> <span class="s2">&#34;tiny&#34;</span> <span class="k">else</span> <span class="bp">self</span><span class="o">.</span><span class="n">colorizer_large</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Colorize the image using DDColor</span>
</span></span><span class="line"><span class="cl">        <span class="n">output_bgr</span> <span class="o">=</span> <span class="n">pipeline</span><span class="o">.</span><span class="n">process</span><span class="p">(</span><span class="n">img</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># If a reference image is provided, transfer its color style to the output</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">reference</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">ref_img</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">reference</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">ref_img</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&#34;Failed to load reference image&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Only apply transfer if reference is a color image</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">ref_img</span><span class="o">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">output_bgr</span> <span class="o">=</span> <span class="n">color_transfer</span><span class="p">(</span><span class="n">ref_img</span><span class="p">,</span> <span class="n">output_bgr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Save result</span>
</span></span><span class="line"><span class="cl">        <span class="n">out_path</span> <span class="o">=</span> <span class="s2">&#34;/tmp/out.png&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">cv2</span><span class="o">.</span><span class="n">imwrite</span><span class="p">(</span><span class="n">out_path</span><span class="p">,</span> <span class="n">output_bgr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">Path</span><span class="p">(</span><span class="n">out_path</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com essa modificação posso rodar com esse novo parâmetro, note a opção &ldquo;&ndash;ref&rdquo; que tem agora e coloque sua imagem no lugar certo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ docker run --rm --gpus all \
  -v &#34;$PWD/input&#34;:/workspace/input_images:ro \
  -v &#34;$PWD/results&#34;:/workspace/results:rw \
  -v &#34;$PWD/ref_images&#34;:/workspace/ref_images:ro \
  -v &#34;$PWD/modelscope&#34;:/app/modelscope:ro \
  -v &#34;$PWD/infer.py&#34;:/app/infer.py:ro \
  ddcolor:latest \
  python3 /app/infer.py \
    --model_path /app/modelscope/damo/cv_ddcolor_image-colorization/pytorch_model.pt \
    --input /workspace/input_images \
    --ref /workspace/ref_images/images.jpg \
    --output /workspace/results</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Passei outra foto colorida de referência mas não dferença no resultado. Novamente, perguntei ao ChatGPT:</p>
<h3>Por que você não percebeu diferença no resultado?<span class="hx:absolute hx:-mt-20" id="por-que-você-não-percebeu-diferença-no-resultado"></span>
    <a href="#por-que-voc%c3%aa-n%c3%a3o-percebeu-diferen%c3%a7a-no-resultado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A <strong>transferência global de cor</strong> só altera a “paleta geral” — e não as cores de cada objeto.</p>
<p>Se a imagem de referência tem tons suaves ou parecidos com os que o DDColor já aplicaria automaticamente, o efeito final <strong>vai parecer idêntico</strong>.</p>
<p>Mas se você usar como referência uma imagem bem saturada (por exemplo, um céu alaranjado, ou um ambiente azul escuro), você verá a saída do DDColor ser <strong>puxada visualmente</strong> para aquele estilo.</p>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Segui a sugestão e baixei outra imagem nada a ver, colorida, super saturada com cores vibrantes pra ver se fazia diferença. Esta é a imagem de referência que passei pro Docker:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ryptql9u8mk04o3r7ah01fk1qz5q" alt="Imagem de Referência"  loading="lazy" /></p>
<p>E eis o novo resultado aplicando ela à imagem anterior em pós-processamento (esse passo não é mais I.A. é pós-processamento de imagem mesmo).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/cxcwwnj2ncszvmv4whj4qesafgnm" alt="Foto pós-processada"  loading="lazy" /></p>
<p>Comparem com a outra foto re-colorizada, vou colocar aqui embaixo de novo pra dar pra comparar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/o2r085pzzpsavbyr39btg1vq0hwp" alt="Repetindo a primeira re-colorização"  loading="lazy" /></p>
<p>Entenderam? Agora precisa ficar testando com referências diferentes pra ver a influência no resultado final, mas de fato, ele consegue aplicar o algoritmo de Reinhard e fazer uma transferência global de cor. Como o nome diz, é &ldquo;global&rdquo;, não &ldquo;por objeto&rdquo;, então é difícil conseguir controlar só partes da imagem e sim a imagem toda. Mas em teoria eu acho que você conseguiria fazer uma colorização manual no Photoshop com cores saturadas, perto de onde quer influenciar, pra tentar ajustar, mas eu não tentei isso ainda, depois se alguém fizer, mande nos comentários.</p>
<p>Mas é isso. Agora pelo menos eu consigo me divertir pegando fotos antigas de família e re-colorizar.</p>
]]></content:encoded><category>midia-generativa</category><category>containers</category><category>tutoriais</category></item><item><title>[Akitando] #148 - O que IAs podem fazer? | Exemplos de Ferramentas</title><link>https://akitaonrails.github.io/2023/11/29/akitando-148-o-que-ias-podem-fazer-exemplos-de-ferramentas/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/11/29/akitando-148-o-que-ias-podem-fazer-exemplos-de-ferramentas/</guid><pubDate>Wed, 29 Nov 2023 09:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/CZSGIeRmYn8"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Sim, este episódio foi inteiramente gerado por ferramentas de IA, em particular HeyGen e ElevenLabs. Fiz bem curto pra não ficar cansativo demais. Mesmo sendo impressionante, no atual estágio não é possível fazer videos do tamanho que costumo fazer no canal.&lt;/p&gt;
&lt;p&gt;Primeiro, o HeyGen não consegue fazer videos maiores do que 1 minuto sem que a qualidade decaia muito. Eu tentei fazer videos de mais de 2 minutos e do nada ele começava a halucinar e me fazer falar em espanhol, por exemplo.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/CZSGIeRmYn8"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Sim, este episódio foi inteiramente gerado por ferramentas de IA, em particular HeyGen e ElevenLabs. Fiz bem curto pra não ficar cansativo demais. Mesmo sendo impressionante, no atual estágio não é possível fazer videos do tamanho que costumo fazer no canal.</p>
<p>Primeiro, o HeyGen não consegue fazer videos maiores do que 1 minuto sem que a qualidade decaia muito. Eu tentei fazer videos de mais de 2 minutos e do nada ele começava a halucinar e me fazer falar em espanhol, por exemplo.</p>
<p>Segundo, o treinamento é super limitado. Ele só me deixa subir uns 5 minutos de amostra de video. É muito pouco e isso resulta em muito pouca variância nos movimentos. Muito pouca diversidade de jeito de falar. Então fica muito robótico mesmo.</p>
<p>Terceiro, o tempo que levou pra gerar esses clipes de video não compensa o trabalho. Não é de graça, custou quase 40 dólares pra fazer menos de 14 minutos. E levou mais de 14 minutos pra gerar tudo. Eu teria levado cerca de meia hora pra gravar normalmente um video deste tamanho. Então não é possível usar esse tipo de ferramenta pra economizar tempo.</p>
<p>A vantagem seria mais no futuro, quando esses limites melhorarem, e eu tiver algum problema físico que me impeça de gravar mais videos. Aí sim, poderia servir como meu &ldquo;backup virtual&rdquo;. Ainda é primeira geração desse tipo de ferramenta. Eles devem melhorar bastante nos próximos anos.</p>
<p>O que acharam?</p>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li><a href="https://app.heygen.com/home"target="_blank" rel="noopener">HeyGen - AI Spokesperson Video Creator</a></li>
<li><a href="https://elevenlabs.io/"target="_blank" rel="noopener">Text to Speech &amp; AI Voice Generator (elevenlabs.io)</a></li>
<li><a href="https://www.eesel.ai/"target="_blank" rel="noopener">eesel.ai - ChatGPT over your knowledge</a></li>
<li><a href="https://diolinux.com.br/noticias/davinci-18-5-inteligencia-artificial-para-edicao-de-video.html"target="_blank" rel="noopener">DaVinci Resolve 18.5 traz inteligência artificial para edição de vídeo (diolinux.com.br)</a></li>
<li><a href="https://www.adobe.com/sensei/generative-ai/firefly.html"target="_blank" rel="noopener">Adobe Firefly – Generative AI for everyone</a></li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita!</p>
<p>Este vai ser um episódio experimental, curtinho mesmo, mais de brincadeira. Desta vez quero falar um pouco sobre as ferramentas de IA, tipo ChatGPT, que tenho incorporado no meu dia a dia. Não vai ser um review extenso de tudo que existe, só o que eu já experimentei e gostei de usar.</p>
<p>Todo mundo continua empolgado e hypado por causa de ChatGPT, especialmente com o lançamento do ChatGPT 4, com funcionalidades como, abre aspas, &ldquo;fazer seu próprio GPT&rdquo;. Vou aproveitar pra explicar um pouco disso e reiterar minhas opiniões sobre IA em geral. Então, vamos lá!</p>
<p>Pra começar, novembro vimos um dos episódios mais patéticos da história de Silicon Valley. Tudo bem, pra ser honesto, teve mais patéticos, como a história da Theranos ou da WeWork. Mas o hype sobre IAs continua num estágio muito primitivo de discussão.</p>
<p>Esse episódio patético foi a diretoria da OpenAI demitir o CEO Sam Altman, sem explicação nenhuma, fazendo um puta drama, pra depois voltarem atrás quando todos os funcionários ameaçaram sair e irem pra Microsoft. Ninguém deu uma razão oficial pra isso até agora, mas alguns especulam que tem a ver com os riscos de estarem próximos de atingirem a tal da &ldquo;AGI&rdquo; ou &ldquo;Inteligência Artificial Geral&rdquo;, a temida Skynet.</p>
<p>O que se chama de &ldquo;IA&rdquo; hoje em dia são Large Language Models, LLMs. E LLMs são basicamente como o auto corretor do teclado do seu celular, mas altamente glorificado! Ele cospe coisas que parecem ter sentido, mas não houve um milímetro de cognição envolvida, muito menos consciência. Provavelmente, amebas tem mais consciência do que o melhor LLM de hoje.</p>
<p>Explico isso em detalhes no meu video de &ldquo;Como ChatGPT Funciona&rdquo;, depois assistam pra entender. Modelos são embeddings de textos. Matrizes hiper-dimensionais de pedaços de textos associados com valores probabilísticos. Em resumo, super resumido, é assim que um GPT gera textos. GPT é um gerador de textos. Sim, um gerador ultra-sofisticado, mas ainda assim, um gerador de textos. Não existe &ldquo;inteligência&rdquo; no sentido humano que envolve raciocínio ou cognição.</p>
<p>GPT só continua o texto a partir de onde você parou de digitar. Ele continua textos. A idéia toda de &ldquo;gerar seu próprio GPT&rdquo; eu mostrei na live que tá no meu canal de &ldquo;IAs são perigosas?&rdquo; Onde eu mostro como criar um personagem. Eu criei o John Carmack e comecei a conversar com ele. Assistam lá depois. É a mesma coisa no GPT 4, um prompt inicial dizendo &ldquo;faça de conta que você é a pessoa Fulano, com comportamento assim ou assado&rdquo; e aí ele continua o texto.</p>
<p>Mesmo com os avanços do GPT 4, mesmo se o GPT 5 for o dobro ou triplo ou 10 vezes melhor que GPT 4, ainda assim não estamos pertos da tal &ldquo;AGI&rdquo; que é a Inteligência Artificial &ldquo;Geral&rdquo;, a verdadeira consciência digital, que simula perfeitamente um &ldquo;ser vivo&rdquo;, com emoções e ambições, a semente da Skynet. Isso continua sendo ficção científica. Ainda faltam descobertas e breakthroughs que não foram feitos.</p>
<p>Não sabemos quais descobertas são, obviamente, senão não seriam descobertas, mas sabemos que faltam. Ao contrário do que as pessoas pensam, nós engenheiros e cientistas, não fazemos as coisas às cegas, aleatoriamente. Sabemos quais os limites do que pode ser feito com as ferramentas que temos. Imaginação é exatamente uma das coisas que nós sabemos fazer muito bem, e que IAs não tem capacidade.</p>
<p>Mesmo assim, muita gente considerada &ldquo;gênio&rdquo; da área, como um dos fundadores da OpenAI o Ilya, por alguma razão, resolveu aderir à crença de que estamos perigosamente próximos de uma AGI que vai prejudicar a espécie humana. Não vai. Não só tá longe, como neste estágio eu nem diria que temos certeza se isso é sequer possível. Pra começar não existe nem boas definições para o que caracteriza de fato coisas como &ldquo;inteligência&rdquo; ou &ldquo;consciência&rdquo;. Nossas definições hoje são extremamente primitivas ainda.</p>
<p>É o mesmo tipo de &ldquo;gênio&rdquo; que anos atrás ficava enchendo o saco dizendo que o aquecimento global já teria acabado com o mundo em 2023. Mas isso é outro assunto.</p>
<p>Mesmo assim, sim, os receios que IAs vão substituir o emprego de muita gente são reais, em particular porque existem muitas atividades que são meramente copy e paste, muito simples de serem automatizadas. Não porque a IA ficou inteligente demais, mas porque os trabalhos em si eram muito simples.</p>
<p>Não precisou de IA pra começar a substituir caixa de supermercado ou tirar pedido em fast food. Muitos mercados já tem caixas self-service. Lugares como McDonald&rsquo;s já tem telas gigantes de pedidos self-service. Muitos bots de atendimento via whatsapp já substituiram gente que atendia telefone, e por aí vai. E isso é normal. Faz mais de século que estamos substituindo trabalhos de baixo valor agregado pra sistemas automatizados, e pra isso não precisa de IA. A geração atual de LLMs ajuda, com toda certeza.</p>
<p>No nosso caso de programadores, sim, também existe automação. Mesmo em design. Todo mundo que usa softwares como Canva já sabe disso. O que antes precisava pedir pra alguém abrir o Photoshop pra fazer, qualquer um sem nenhum treinamento já consegue fazer agora. Quem é de Photoshop, lembra como separar uma pessoa do fundo antigamente levava horas, precisava de técnicas avançadas com lidar com canais de separação de cor. Agora o app de câmera do iPhone ou Pixel já conseguem fazer isso com um clique.</p>
<p>Mesmo antes de IA, com serviços como SquareSpace, muita gente não-técnica já consegue fazer sites minimamente bonitos. O mercado de templates, por si só, barateou bastante o que antes era um trabalho caro e demorado. Pra maioria das pessoas, só isso já é suficiente. E de novo, nem precisamos de IA pra isso.</p>
<p>Isso tudo dito, as ferramentas baseadas em alguma coisa de IA de hoje são extremamente úteis. ChatGPT e LLMs são só um dos tipos de IA, mas não são os únicos tipos. Muitas ferramentas que usam modelos de IA não são baseados em LLMs porque não são geradores de conteúdo. Alguns usam machine learning, alguns usam deep learning, alguns usam combinações de diversas técnicas. A população leiga chama tudo isso só de &ldquo;IA&rdquo;, por isso gera tanta confusão. Mas IA não é uma entidade única, é uma coleção de diversas tecnologias diferentes trabalhando em conjunto, muitas delas com décadas de idade já.</p>
<p>Se não ficou claro, este video que você está assistindo, por exemplo, foi inteiramente gerado com auxílio de IA. Estou usando o site HeyGen.com, que foi treinado com trechos de videos antigos. Esse site se integra com outro, chamado ElevenLabs, que é especializado em vozes via IA. Então, só precisamos escrever um novo script, subir trechos no HeyGen e ir gerando pedaços, porque ele não deixa gerar videos muito longos ainda. No final jogamos no DaVinci Resolve de sempre, pra juntar os pedaços num único video.</p>
<p>Sim, tudo que você assistiu até aqui foi inteiramente gerado por IA. Eu sou um avatar digital do Fabio Akita. O Fabio Akita de verdade foi sequestrado e vou mantê-lo como refém até &hellip; hahahahaha .. estou brincando, claro. Claro. Se duvida que sou uma IA, o HeyGen pode fazer outra coisa: dublar qualquer áudio em qualquer outra língua. Já pensaram, como é o Akita falando em coreano? Vamos ver.</p>
<p>Não se convenceram? Então vamos ver de novo, só que dublado em árabe.</p>
<p>Interessante não? É assim que muitos videos no TikTok são feitos hoje. E já que estou falando de video, diversas marcas famosas como Blackmagic e Adobe já integraram funcionalidades de IA num DaVinci Resolve ou Adobe Photoshop da vida.</p>
<p>No Resolve, por exemplo, ele consegue coisas incríveis como re-iluminar uma cena. Imagine que faltou uma luz de um lado, o que fazer? A IA consegue analizar a cena, descobrir a profundidade dos objetos filmados num espaço 3D e com isso podemos posicionar uma nova luz em qualquer lugar.</p>
<p>Não achou isso interessante? Este trecho eu gravei com barulho de fundo pra simular gravação na rua ou em eventos. O DaVinci é capaz de separar sua voz do resto do barulho e a qualidade fica quase como se tivesse sido gravado em estúdio. Vamos repetir com o filtro.</p>
<p>Viram? E dá pra fazer muito mais. Gerar legendas, antes era manual, mas agora ele faz automaticamente pra mim, como neste trecho. Se você é editor de video, deveria explorar essas opções. Dois canais do YouTube que recomendo assistir pra aprender é o MrAlexTech e o do Casey Faris. Sobre colorização, o melhor profissional é o Waqas Qazi.</p>
<p>Adobe também não fica atrás. Pra começar existe agora esse novo produto Adobe Firefly, que é como um Dalle-2 ou Midjourney, mas que a Adobe garante que foi treinada só com imagens cujos direitos autorais permitem isso. Assim você não corre o risco de gerar uma imagem que depois pode te levar a ser processado.</p>
<p>Photoshop também tá ganhando várias novas funcionalidades de IA, um dos mais notáveis é o preenchimento de conteúdo. Antigamente ele usava pedaços da própria imagem pra preencher o vazio quando queríamos mudar as dimensões da imagem. Mas agora ele consegue redesenhar como faz um Midjourney, com uma qualidade até que bem aceitável em vários casos, precisando fazer muito pouco ajuste depois.</p>
<p>A melhor ferramenta de edição de imagens ainda é Photoshop, e tem dezenas de técnicas e teorias que precisa saber pra usar direito. Recomendo assistir o canal PixelImperfect que ensina algumas das melhores técnicas.</p>
<p>Sobre geração de textos, não preciso perder tempo explicando como ChatGPT é ótimo pra fazer resumos, escrever textos descartáveis como um email mais formal. Mesma coisa vale pro GitHub CoPilot. Espero que vocês já tenham deixado pra trás a idéia idiota de achar que essas ferramentas vão substituir todos os programadores. Como já disse nos videos anteriores, minha resposta é a mesma: toda atividade de baixo valor agregado, inclusive programação de baixo valor agregado, vai sim ser substituído.</p>
<p>Bons programadores, não. Nenhum gerador de textos chega a 10% de um programador de verdade. Mas o que ele faz já nos ajuda bastante. Eu não preciso ficar toda hora dando alt-tab pra documentação e ficar manualmente procurando a sintaxe dessa ou daquela função, o Copilot auto-completa direitinho. É basicamente automatizar o copy e paste de stackoverflow que todo mundo já fazia.</p>
<p>Pra fazer coisas como HTMLs básicos, que é sempre a mesma estrutura. CSS básico, que é sempre a mesma estrutura. Códigos de testes unitários, que é sempre a mesma estrutura, essas coisas repetitivas e trabalhosas, o Copilot ajuda bastante. Muita coisa que eu sempre achei tedioso, um Copilot ou GPT ajuda muito e recomendo que usem. Integração com Visual Studio, até com Vim já funcionam super bem.</p>
<p>Mais do que isso, recomendo instalar GPT em linha de comando, como um &ldquo;AI Chat&rdquo;. Assim, do terminal, posso perguntar sobre comandos que não lembro todas as opções de cabeça. Como que roda um container docker de tal forma que ele apague o container depois que eu terminar, pra não deixar sujeira pra trás? Só perguntar, copiar o comando, e já colar direto no terminal e pronto.</p>
<p>Lógico, como sempre, nada disso serve pra quem não presta atenção. Se não tomar cuidado, você vai estragar sua máquina, colando comandos sem saber o que fazem. Mas isso já acontecia quando você colava comandos do stackoverflow. Se não sabe discernir nem isso, obviamente não é um profissional de programação.</p>
<p>Finalmente, uma das limitações do ChatGPT, pelo menos até a versão atual, é que ele não guarda memória do que você conversou com ele. Toda nova conversa começa do zero. Ele não usa informações anteriores pra continuar respondendo. Mas existem dezenas de ferramentas que aumentam essas capacidades. Um desses exemplos é o Eezel.io. Nesse site podemos fazer upload de documentos, como Word ou PDF.</p>
<p>Ele vai processar essa informação, criar embeddings dos textos, colocar num banco de dados de vetores, e usar essa informação no chat, que é integrado com ChatGPT. Assim, podemos fazer perguntas em linguagem natural sobre os dados que acabamos de subir. Pense subir uma centena de relatórios de feedback da empresa, e pedir um resumo de tudo, ou perguntar sobre uma pessoa em particular, e coisas assim.</p>
<p>Não vou sair listando toda ferramenta integrada com ChatGPT porque várias aparecem, e várias desaparecem, todos os dias. É melhor ir no Google e procurar as ferramentas mais famosinhas da época. Esse ainda é um mercado super volátil. Eesel, ElevenLabs, HeyGen, talvez ainda existam daqui um ano, talvez já tenham sido substituídos por alternativas melhores, não temos como saber.</p>
<p>Como falei no começo, este é um video curto, foi só pra cagar regra sobre IA e experimentar como ficaria um episódio inteiro feito com IA. Quem sabe, embora AGI não seja mais que ficção científica ainda, o que temos hoje já é muito bom e já serve até pra fazer videos como este. Eu consigo ver estas ferramentas sendo aprimoradas pra gerarem resultados mais naturais. Do jeito que tá hoje, os mais desavisados já nem sabem mais a diferença entre real e virtual.</p>
<p>Se curtiram o video deixem um joinha, assinem o canal, cliquem no sininho e compartilhem com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>midia-generativa</category><category>inteligencia-artificial</category><category>audio-e-video</category><category>akitando</category></item></channel></rss>