<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nuvem Dev</title>
    <description>The latest articles on DEV Community by Nuvem Dev (@nuvemdev).</description>
    <link>https://dev.to/nuvemdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4199270%2F91dc8b9f-e13b-4b68-86aa-55cc6eb3b7ce.png</url>
      <title>DEV Community: Nuvem Dev</title>
      <link>https://dev.to/nuvemdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nuvemdev"/>
    <language>en</language>
    <item>
      <title>Narração em português com IA, de graça, no Linux (Piper TTS + ffmpeg)</title>
      <dc:creator>Nuvem Dev</dc:creator>
      <pubDate>Fri, 23 Oct 2026 23:09:43 +0000</pubDate>
      <link>https://dev.to/nuvemdev/narracao-em-portugues-com-ia-de-graca-no-linux-piper-tts-ffmpeg-575h</link>
      <guid>https://dev.to/nuvemdev/narracao-em-portugues-com-ia-de-graca-no-linux-piper-tts-ffmpeg-575h</guid>
      <description>&lt;p&gt;&lt;a href="https://nuvemdev.substack.com/p/voz-de-ia-em-portugues-por-us-1-e" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fewkywwe6r8dhnhcvrw40.jpg" alt="Short de 32 s narrado com Piper" width="720" height="1280"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Esse short de 32 segundos foi narrado inteiro com o que tá nesse tutorial. Clica pra ver (ele roda na newsletter).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Eu precisava de narração em português pros meus vídeos curtos e não queria depender de API paga logo de cara. A saída foi o &lt;a href="https://github.com/OHF-Voice/piper1-gpl" rel="noopener noreferrer"&gt;Piper&lt;/a&gt;: um TTS open source (GPL-3.0) que roda offline, na CPU, e tem voz pt-BR.&lt;/p&gt;

&lt;p&gt;Aqui vai o caminho que eu usei, do zero até um mp4 vertical. Rodei tudo isso num Debian, com Python 3.12, em outubro de 2026, com o &lt;code&gt;piper-tts&lt;/code&gt; 1.8.0.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Instalar
&lt;/h2&gt;

&lt;p&gt;Cria uma venv pra não bagunçar o Python do sistema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;narracao &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;narracao
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install &lt;/span&gt;piper-tts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não precisa de GPU. O pacote já vem com o que ele usa pra fonemas (espeak-ng embutido).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Baixar a voz pt-BR
&lt;/h2&gt;

&lt;p&gt;A voz brasileira que eu uso é a &lt;code&gt;faber&lt;/code&gt;, qualidade medium (dataset CC0, segundo o model card):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; piper.download_voices pt_BR-faber-medium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso baixa dois arquivos na pasta atual: &lt;code&gt;pt_BR-faber-medium.onnx&lt;/code&gt; (uns 63 MB) e o &lt;code&gt;.onnx.json&lt;/code&gt; de config. Os dois precisam ficar juntos.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Primeiro áudio
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Fala, pessoal! Essa narração foi gerada de graça, rodando no meu servidor."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; roteiro.txt
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; piper &lt;span class="nt"&gt;-m&lt;/span&gt; pt_BR-faber-medium.onnx &lt;span class="nt"&gt;-i&lt;/span&gt; roteiro.txt &lt;span class="nt"&gt;-f&lt;/span&gt; narracao.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aqui levou menos de 2 segundos pra gerar uns 4 segundos de áudio, na CPU. Confere o arquivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffprobe &lt;span class="nt"&gt;-v&lt;/span&gt; error &lt;span class="nt"&gt;-show_entries&lt;/span&gt; &lt;span class="nv"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;duration:stream&lt;span class="o"&gt;=&lt;/span&gt;sample_rate,channels &lt;span class="nt"&gt;-of&lt;/span&gt; &lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;nw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 narracao.wav
&lt;span class="c"&gt;# sample_rate=22050&lt;/span&gt;
&lt;span class="c"&gt;# channels=1&lt;/span&gt;
&lt;span class="c"&gt;# duration=4.179592&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flags que valem conhecer (&lt;code&gt;python3 -m piper --help&lt;/code&gt; mostra todas):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--length-scale 1.1&lt;/code&gt; deixa a fala mais lenta; &lt;code&gt;0.9&lt;/code&gt; acelera&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--sentence-silence 0.3&lt;/code&gt; coloca pausa entre frases&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--volume 1.2&lt;/code&gt; aumenta o volume&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Em lote, com Python
&lt;/h2&gt;

&lt;p&gt;Pra vídeo eu gosto de gerar um arquivo por cena, porque fica fácil sincronizar legenda e imagem depois. Uma linha do roteiro = uma cena:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# narrar.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wave&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;piper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PiperVoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SynthesisConfig&lt;/span&gt;

&lt;span class="n"&gt;voz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PiperVoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pt_BR-faber-medium.onnx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SynthesisConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length_scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# &amp;lt; 1 = fala mais rápido
&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;blocos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;texto&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blocos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;wave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parte_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;wav&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;voz&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synthesize_wav&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wav&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syn_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parte_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.wav  &amp;lt;- &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;texto&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"Primeira frase do roteiro.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Segunda frase, um pouco mais longa, pra testar o ritmo.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; roteiro2.txt
python3 narrar.py roteiro2.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dica de quem apanhou: nome de marca em inglês ele lê do jeito dele. Eu mantenho duas versões do texto, uma pra legenda ("ElevenLabs") e outra pra voz, escrita do jeito que eu quero ouvir ("Íleven Labs").&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Juntar e normalizar o volume
&lt;/h2&gt;

&lt;p&gt;Plataforma de vídeo curto costuma trabalhar perto de -14 LUFS. O &lt;code&gt;loudnorm&lt;/code&gt; do ffmpeg resolve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in &lt;/span&gt;parte_&lt;span class="k"&gt;*&lt;/span&gt;.wav&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"file '&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; lista.txt
ffmpeg &lt;span class="nt"&gt;-f&lt;/span&gt; concat &lt;span class="nt"&gt;-safe&lt;/span&gt; 0 &lt;span class="nt"&gt;-i&lt;/span&gt; lista.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="nv"&gt;loudnorm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;I&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-14&lt;/span&gt;:TP&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-1&lt;/span&gt;.5:LRA&lt;span class="o"&gt;=&lt;/span&gt;11 &lt;span class="nt"&gt;-ar&lt;/span&gt; 44100 narracao_final.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pra conferir quanto deu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; narracao_final.wav &lt;span class="nt"&gt;-af&lt;/span&gt; ebur128 &lt;span class="nt"&gt;-f&lt;/span&gt; null - 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A1&lt;/span&gt; Integrated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Num clipe curto o loudnorm de uma passada fica um pouco abaixo do alvo (aqui deu -14,9). Pra short isso tá ok; se quiser cravar, faz o loudnorm em duas passadas.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Virar vídeo vertical
&lt;/h2&gt;

&lt;p&gt;O mínimo: uma imagem 1080x1920 + a narração.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-loop&lt;/span&gt; 1 &lt;span class="nt"&gt;-i&lt;/span&gt; capa.png &lt;span class="nt"&gt;-i&lt;/span&gt; narracao_final.wav &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1080:1920,format=yuv420p"&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; 30 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-tune&lt;/span&gt; stillimage &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 160k &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-shortest&lt;/span&gt; video.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffprobe &lt;span class="nt"&gt;-v&lt;/span&gt; error &lt;span class="nt"&gt;-show_entries&lt;/span&gt; &lt;span class="nv"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;codec_name,width,height,r_frame_rate &lt;span class="nt"&gt;-of&lt;/span&gt; compact video.mp4
&lt;span class="c"&gt;# stream|codec_name=h264|width=1080|height=1920|r_frame_rate=30/1&lt;/span&gt;
&lt;span class="c"&gt;# stream|codec_name=aac|r_frame_rate=0/0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No meu pipeline eu vou além disso (uma cena por imagem com zoom lento via &lt;code&gt;zoompan&lt;/code&gt;, legenda em &lt;code&gt;.ass&lt;/code&gt; queimada com o filtro &lt;code&gt;ass=&lt;/code&gt;), mas o esqueleto é esse aí.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limites, sendo sincero
&lt;/h2&gt;

&lt;p&gt;A voz é boa pra tutorial e pra testar formato. Mas a entonação é mais reta que a dos serviços pagos e às vezes escorrega numa palavra. Se o projeto pede voz bem natural, ou clonar a sua, aí vale olhar ElevenLabs ou Murf. Eu comparei preço por minuto das duas (e da Fish Audio, que hoje não lista português no TTS) &lt;a href="https://nuvemdev.substack.com/p/voz-de-ia-em-portugues-por-us-1-e" rel="noopener noreferrer"&gt;nessa edição da newsletter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Se travar em algum passo, comenta aqui com o erro que eu tento ajudar.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Alguns links da newsletter são de afiliado: se você comprar, eu ganho uma comissão e você não paga nada a mais.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>tutorial</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
