{"id":29091057,"url":"https://github.com/luizomf/sussu","last_synced_at":"2025-10-30T17:36:29.955Z","repository":{"id":299078230,"uuid":"1001986838","full_name":"luizomf/sussu","owner":"luizomf","description":"CLI educacional para transcrição com OpenAI Whisper","archived":false,"fork":false,"pushed_at":"2025-06-27T00:47:52.000Z","size":164,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-27T01:29:00.335Z","etag":null,"topics":["asr","asr-model","att","json","openai","speech-to-text","srt","stt","subtitles","sussu","transcription","tsv","txt","vtt","whisper"],"latest_commit_sha":null,"homepage":"https://www.otaviomiranda.com.br/2025/python-sussu-cli-openai-whisper/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/luizomf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-14T13:21:16.000Z","updated_at":"2025-06-27T00:47:56.000Z","dependencies_parsed_at":"2025-06-14T15:28:56.324Z","dependency_job_id":null,"html_url":"https://github.com/luizomf/sussu","commit_stats":null,"previous_names":["luizomf/sussu"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/luizomf/sussu","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luizomf%2Fsussu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luizomf%2Fsussu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luizomf%2Fsussu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luizomf%2Fsussu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/luizomf","download_url":"https://codeload.github.com/luizomf/sussu/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luizomf%2Fsussu/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262382711,"owners_count":23302292,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asr","asr-model","att","json","openai","speech-to-text","srt","stt","subtitles","sussu","transcription","tsv","txt","vtt","whisper"],"created_at":"2025-06-28T06:01:48.394Z","updated_at":"2025-10-30T17:36:29.945Z","avatar_url":"https://github.com/luizomf.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sussu(rro): CLI educacional com OpenAI Whisper\n\n\u003e Ferramenta de linha de comando focada em educação e IA offline. Utiliza o\n\u003e poder do Whisper da OpenAI para transcrever áudios e vídeos de forma simples e\n\u003e eficiente.\n\nEsses são todos os links que menciono nos dois vídeos:\n\n- [(🚫 FAILED) whisper em tempo real via microfone](https://www.otaviomiranda.com.br/2025/whisper-live-sera-que-deu-certo/)\n- [logging — Logging facility for Python](https://docs.python.org/3/library/logging.html)\n- [sussu(rro): CLI educacional com OpenAI Whisper](https://www.otaviomiranda.com.br/2025/python-sussu-cli-openai-whisper/)\n- [openai / whisper](https://github.com/openai/whisper)\n\n### 🎥 Parte 1 — CLI do Whisper:\n\n- [Whisper OpenAI: Guia Completo de Transcrição com Inteligência Artificial (vídeo e áudio)](https://youtu.be/y15070biffg)\n\n### 💻 Parte 2 — Código do `sussu` com Python + Whisper:\n\n- [Whisper com Python: transcrição de áudio com Inteligência Artificial no seu projeto](https://youtu.be/SAt7yuO8DX4)\n\n---\n\n## Sobre o Whisper e a OpenAI\n\nEstava curioso para saber se a OpenAI usa o Whisper em seus próprios produtos,\nentão questionei o ChatGPT...\n\n\u003e **Eu:** A OpenAI usa o Whisper internamente em algum recurso que as pessoas\n\u003e consomem? \\\n\u003e **ChatGPT:** Sim, a OpenAI usa o Whisper como base para recursos de\n\u003e transcrição e compreensão de áudio em produtos como o ChatGPT com entrada de\n\u003e voz. Ele também é utilizado em APIs que envolvem reconhecimento de fala, como\n\u003e a `speech-to-text` da OpenAI.\n\n---\n\n## Sobre este documento:\n\nEstou tentando manter este documento (`README.md`) sincronizado com o link\nabaixo. Caso não encontre o que procura aqui, acesse:\n\n- [Transcreva áudio com Python: Sussu CLI + OpenAI Whisper](https://www.otaviomiranda.com.br/2025/python-sussu-cli-openai-whisper/)\n\n---\n\n## Introdução geral\n\nAo rodar este projeto, uma das primeiras coisas que você vai querer fazer é usar\no comando `whisper` para fazer a transcrição inicial de algum vídeo ou áudio.\nEssa transcrição é um ótimo jeito de ver na prática como o **Whisper** trabalha\ne o que esperar dos resultados.\n\n- [Repositório oficial do `whisper`](https://github.com/openai/whisper)\n- [Repositório do `sussu`](https://github.com/luizomf/sussu)\n\nVamos começar pela **instalação** do projeto, isso já coloca os comandos `sussu`\ne `whisper` funcionando direto no seu terminal.\n\n---\n\n## Instalação do `sussu`\n\nSe você encontrar alguma dificuldade com o ambiente, recomendo meu tutorial\ncompleto:\n\n- [Ambiente Python Moderno 2025: UV, Ruff, Pyright, pyproject.toml e VS Code](https://www.youtube.com/watch?v=HuAc85cLRx0)\n\nEste projeto utiliza o **Python 3.11.9** por questões de compatibilidade com o\n**Whisper**. Evite alterar essa versão se não souber o que está fazendo, pois\n**eu já testei tudo para você**.\n\nAlém disso, este projeto usa o [`uv`](https://docs.astral.sh/uv/) para o\ngerenciamento geral (pacotes, versão do Python, etc.).\n\nPara instalar tudo, basta rodar o comando:\n\n```sh\n# Se ainda não clonou o repositório\ngit clone https://github.com/luizomf/sussu.git\n# Acesse a pasta do projeto\ncd sussu\n# Sincronizando\nuv sync\n# é só isso mesmo 😅\n```\n\n`uv sync` é suficiente para:\n\n- Baixar e instalar o `python 3.11.9`\n- Criar o ambiente virtual em `.venv`\n- Instalar os pacotes necessários\n- Buildar o `whisper` e o `sussu`\n\n---\n\n## `ffmpeg`\n\nVocê também precisará ter o **`ffmpeg`** instalado. Ele é um software de código\naberto com várias ferramentas e bibliotecas para trabalhar com arquivos\nmultimídia, especialmente áudio e vídeo. Embora o `whisper` foque na transcrição\nde áudio, o `ffmpeg` é quem permite que você transcreva seus vídeos diretamente,\nsem precisar convertê-los para áudio antes.\n\nPara instalar o `ffmpeg` no seu sistema, você pode usar um dos comandos abaixo.\nEles foram retirados diretamente do\n[repositório oficial do `whisper`](https://github.com/openai/whisper):\n\n```bash\n# No Ubuntu ou Debian\nsudo apt update \u0026\u0026 sudo apt install ffmpeg\n\n# No Arch Linux\nsudo pacman -S ffmpeg\n\n# No macOS com Homebrew (https://brew.sh/)\nbrew install ffmpeg\n\n# No Windows com Chocolatey (https://chocolatey.org/)\nchoco install ffmpeg\n\n# No Windows usando Scoop (https://scoop.sh/)\nscoop install ffmpeg\n\n# Adicional: No Windows usando winget (https://winstall.app/apps/Gyan.FFmpeg)\nwinget install --id=Gyan.FFmpeg -e\n```\n\n**Observação:** Dos comandos listados, os únicos que testei e aprovei (✅) foram\nos para **macOS** e **Ubuntu**.\n\n---\n\n## Rodando pela Primeira Vez\n\nPara verificar se tudo foi instalado corretamente, você tem duas opções:\n**ativar o ambiente virtual** ou usar o comando **`uv run`**. Sugiro que você\nteste com `whisper -h`. Esse comando deve exibir a ajuda completa do `whisper`,\nindicando que ele está funcionando. Veja os exemplos:\n\n```bash\nuv run whisper -h\n# Ou, se você já ativou o ambiente virtual\nwhisper -h\n```\n\n**Observação:** Editores de código como **VS Code** ou **Zed** podem ativar o\nambiente virtual automaticamente ao abrir um novo terminal, desde que estejam\nconfigurados corretamente. Se for o seu caso, basta fechar e abrir o terminal\nnovamente para que as mudanças façam efeito.\n\n---\n\n## `whisper -h`: Entendendo Alguns Argumentos Importantes\n\nAo digitar `whisper -h` ou `whisper --help`, você pode se surpreender com a\nquantidade de argumentos disponíveis. Mas não se preocupe! Você não precisa\nsaber o que cada um deles faz. Na verdade, a maioria dos argumentos já vem com\nvalores padrão que funcionam perfeitamente. No entanto, se você quiser\npersonalizar um pouco o comportamento da ferramenta, vamos analisar alguns dos\nmais importantes.\n\nO `whisper` utiliza a biblioteca `argparse` do Python para gerar essa\ndocumentação de ajuda (`help`) completa e bem organizada. Se você tiver\ninteresse em aprender mais sobre como criar interfaces de linha de comando\nprofissionais com Python, confira meu vídeo:\n\n- [Python e argparse: Do Zero a uma CLI Profissional (Projeto Real na Prática)](https://www.youtube.com/watch?v=Ad6934NXn4A)\n\n---\n\n### Argumentos Essenciais do `whisper`\n\nVamos começar com os argumentos que você usará com mais frequência:\n\n**`audio`**: Este é o **argumento posicional** principal. Ele representa o\ncaminho completo (localização) do arquivo de áudio ou vídeo que você quer\ntranscrever.\n\n**Exemplo:**\n\n```bash\nwhisper /caminho/do/seu/arquivo.mp4\n```\n\nNo exemplo acima, você notou que especificamos apenas o caminho do arquivo de\nvídeo. Nas próximas seções, vou detalhar as opções que mais utilizo para\npersonalizar a transcrição.\n\n---\n\n**`--model MODEL`**: Este argumento serve para **definir qual modelo será usado\nna transcrição** do seu áudio ou vídeo. Ele é opcional, e o valor padrão é\n`turbo`. O modelo `turbo` é excelente: rápido e multilíngue, mas requer cerca de\n**6GB de VRAM** para rodar.\n\nTalvez você queira usar outros modelos que exigem mais ou menos recursos do seu\nhardware, ou que possuem mais ou menos parâmetros (como `base`, `small`,\n`medium`, etc.).\n\nAqui estão os modelos disponíveis e seus requisitos aproximados de VRAM:\n\n- **`tiny`**: 39M parâmetros, `tiny.en` e `tiny`, VRAM ~1 GB\n- **`base`**: 74M parâmetros, `base.en` e `base`, VRAM ~1 GB\n- **`small`**: 244M parâmetros, `small.en` e `small`, VRAM ~2 GB\n- **`medium`**: 769M parâmetros, `medium.en` e `medium`, VRAM ~5 GB\n- **`large`**: 1550M parâmetros, `large`, `large-v2` e `large-v3`, VRAM ~10 GB\n- **`turbo`**: 809M parâmetros, `turbo`, VRAM ~6 GB\n\n**VRAM** é um tipo de memória RAM especializada que as placas de vídeo (GPUs)\nusam. Mas não se preocupe se você não tiver uma placa de vídeo dedicada! Se seu\ncomputador compartilha a RAM com a GPU, o que acontece em Macs com chips Apple\nSilicon (M1, M2, M3 e posteriores), por exemplo, você conseguirá usar os modelos\ndo Whisper normalmente.\n\nNesses casos, o que realmente limita é a **quantidade total de memória RAM\ndisponível no seu sistema**. Por exemplo: se você tem apenas 8GB de RAM, o ideal\né testar os modelos `tiny`, `base` ou `small`.\n\nA partir do modelo `medium`, é bem provável que você perceba uma **queda\ndrástica no desempenho geral da sua máquina**, já que a memória será\ncompletamente consumida.\n\n**Exemplo:**\n\n```bash\nwhisper /caminho/do/seu/arquivo.mp4 --model large-v2\n```\n\n---\n\n**`--device DEVICE`**: Este argumento é para você que possui uma **placa de\nvídeo NVIDIA com drivers CUDA** e uma versão compatível com o PyTorch. Se for o\nseu caso, vale a pena usar `--device cuda` para aproveitar o processamento da\nGPU. Caso contrário, não se preocupe em alterar esta opção, o padrão é `cpu`\n(processamento pela CPU) e funcionará perfeitamente.\n\n**Exemplo:**\n\n```bash\nwhisper /caminho/do/seu/arquivo.mp4 --model large-v2 --device cpu\n```\n\n---\n\n**`--output_dir` ou `-o`**: Define o **caminho da pasta onde as transcrições\nserão salvas**. Por padrão, os arquivos serão salvos na raiz do projeto (`.`).\n\n---\n\n**`--output_format` ou `-f`**: Permite que você escolha o **formato da\ntranscrição ou legenda** gerada. As opções disponíveis são: `txt`, `vtt`, `srt`,\n`tsv`, `json` e `all` (que gera todos os formatos). O padrão é `all`.\n\n---\n\n**Exemplo:**\n\nO arquivo de saída será `srt` (SubRip) na pasta indicada em `-o`. Essa pasta\nserá criada caso não exista.\n\n```bash\nwhisper /caminho/do/seu/arquivo.mp4 --model turbo -o caminho/da/pasta_de_saida -f srt\n```\n\n---\n\n**`--task`**: Com este argumento, você pode escolher entre **transcrever o\náudio** no idioma original ou **traduzir para o inglês**. As opções são\n`transcribe` (o padrão, que transcreve no idioma falado no áudio) ou `translate`\n(que traduz o conteúdo para o inglês).\n\n**Exemplo:**\n\n```bash\nwhisper /caminho/do/seu/arquivo.mp4 --model turbo --task transcribe\n```\n\n---\n\n**`--language`**: Este argumento permite que você **especifique o idioma falado\nno áudio ou vídeo**. Existem muitas opções de idiomas disponíveis. Se você não\ninformar esse argumento, o `whisper` é inteligente o suficiente para detectar\nautomaticamente o idioma do conteúdo.\n\n---\n\nForma curta (language code):\n\n```python\n[\"af\", \"am\", \"ar\", \"as\", \"az\", \"ba\", \"be\", \"bg\", \"bn\", \"bo\", \"br\", \"bs\", \"ca\",\n\"cs\", \"cy\", \"da\", \"de\", \"el\", \"en\", \"es\", \"et\", \"eu\", \"fa\", \"fi\", \"fo\", \"fr\",\n\"gl\", \"gu\", \"ha\", \"haw\", \"he\", \"hi\", \"hr\", \"ht\", \"hu\", \"hy\", \"id\", \"is\", \"it\",\n\"ja\", \"jw\", \"ka\", \"kk\", \"km\", \"kn\", \"ko\", \"la\", \"lb\", \"ln\", \"lo\", \"lt\", \"lv\",\n\"mg\", \"mi\", \"mk\", \"ml\", \"mn\", \"mr\", \"ms\", \"mt\", \"my\", \"ne\", \"nl\", \"nn\", \"no\",\n\"oc\", \"pa\", \"pl\", \"ps\", \"pt\", \"ro\", \"ru\", \"sa\", \"sd\", \"si\", \"sk\", \"sl\", \"sn\",\n\"so\", \"sq\", \"sr\", \"su\", \"sv\", \"sw\", \"ta\", \"te\", \"tg\", \"th\", \"tk\", \"tl\", \"tr\",\n\"tt\", \"uk\", \"ur\", \"uz\", \"vi\", \"yi\", \"yo\", \"yue\", \"\", \"zh\"]\n```\n\n- Exemplo para português do Brasil: `--language pt`\n\n---\n\nForma longa (language name):\n\n```python\n[\"Afrikaans\", \"Albanian\", \"Amharic\", \"Arabic\", \"Armenian\", \"Assamese\",\n\"Azerbaijani\", \"Bashkir\", \"Basque\", \"Belarusian\", \"Bengali\", \"Bosnian\",\n\"Breton\", \"Bulgarian\", \"Burmese\", \"Cantonese\", \"Castilian\", \"Catalan\",\n\"Chinese\", \"Croatian\", \"Czech\", \"Danish\", \"Dutch\", \"English\", \"Estonian\",\n\"Faroese\", \"Finnish\", \"Flemish\", \"French\", \"Galician\", \"Georgian\", \"German\",\n\"Greek\", \"Gujarati\", \"Haitian\", \"Haitian Creole\", \"Hausa\", \"Hawaiian\", \"Hebrew\",\n\"Hindi\", \"Hungarian\", \"Icelandic\", \"Indonesian\", \"Italian\", \"Japanese\",\n\"Javanese\", \"Kannada\", \"Kazakh\", \"Khmer\", \"Korean\", \"Lao\", \"Latin\", \"Latvian\",\n\"Letzeburgesch\", \"Lingala\", \"Lithuanian\", \"Luxembourgish\", \"Macedonian\",\n\"Malagasy\", \"Malay\", \"Malayalam\", \"Maltese\", \"Mandarin\", \"Maori\", \"Marathi\",\n\"Moldavian\", \"Moldovan\", \"Mongolian\", \"Myanmar\", \"Nepali\", \"Norwegian\",\n\"Nynorsk\", \"Occitan\", \"Panjabi\", \"Pashto\", \"Persian\", \"Polish\", \"Portuguese\",\n\"Punjabi\", \"Pushto\", \"Romanian\", \"Russian\", \"Sanskrit\", \"Serbian\", \"Shona\",\n\"Sindhi\", \"Sinhala\", \"Sinhalese\", \"Slovak\", \"Slovenian\", \"Somali\", \"Spanish\",\n\"Sundanese\", \"Swahili\", \"Swedish\", \"Tagalog\", \"Tajik\", \"Tamil\", \"Tatar\",\n\"Telugu\", \"Thai\", \"Tibetan\", \"Turkish\",\"Turkmen\", \"Ukrainian\", \"Urdu\", \"Uzbek\",\n\"Valencian\", \"Vietnamese\", \"Welsh\", \"Yiddish\", \"Yoruba\"]\n```\n\n- Exemplo para português do Brasil: `--language Portuguese`\n\nSe precisar de um dicionário completo com todos os idiomas e seus códigos, ele\nestá disponível em `whisper.tokenizer.LANGUAGES` dentro do código do `whisper`.\n\n**Exemplo:**\n\n```bash\n# Para o comando ficar menor, vou manter tudo padrão\n# model turbo (padrão)\n# task transcribe (padrão)\n# etc...\n# Idioma falado no vídeo \"Português\"\nwhisper /caminho/do/seu/arquivo.mp4 --language pt\n```\n\n---\n\n**`--temperature`:** controla a \"criatividade\" do modelo. Vai de `0.0` a `1.0`.\nQuanto mais alto, mais liberdade o modelo tem pra decidir os próximos tokens.\nEsse parâmetro interage com `--beam_size`, `--patience` e `--best_of`.\n\n---\n\n**`--beam_size`:** número de hipóteses que o modelo mantém em paralelo. Pensa\ncomo se ele testasse vários caminhos ao mesmo tempo e no fim escolhesse o\nmelhor. O padrão é `5` e **só funciona se `--temperature == 0.0`**.\n\n---\n\n**`--patience`:** fator de tolerância que faz o modelo continuar explorando\nnovas hipóteses mesmo depois de achar uma aceitável. Requer\n`--temperature == 0.0` e `--beam_size \u003e 1`.\n\n---\n\n**`--best_of`:** número de amostras diferentes geradas antes de escolher a\nmelhor. Funciona apenas quando `--temperature \u003e 0.0`.\n\n---\n\n**Cola rápida:**\n\n```\n- temperature \u003e 0 → usa sampling\n  ✅ --best_of 5 (5 amostras)\n  🔴 --beam_size (ignorado)\n  🔴 --patience (ignorado)\n\n- temperature == 0 → usa beam search\n  ✅ --beam_size 5 (5 hipóteses)\n  ✅ --patience 2 (2 x 5 = 10 hipóteses)\n  🔴 --best_of (ignorado)\n\n- temperature == 0 → greedy\n  ✅ --beam_size 1 (1 hipótese)\n  🔴 --patience (não faz diferença)\n  🔴 --best_of (ignorado)\n```\n\n**Importante:** Quanto maiores os valores de `--beam_size`, `--patience` e\n`--best_of`, mais lento e \"indeciso\" o modelo tende a ficar. Isso acontece\nporque ele precisa gerar mais hipóteses ou amostras e, em seguida, tomar uma\ndecisão entre elas. Faça testes rápidos para confirmar esse comportamento.\n\n**Observação sincera:**\n\nNa prática, o modelo vai responder como foi treinado, independente do seu\ncapricho nas configs. Trocar `temperature`, `beam_size`, `patience` e afins pode\nvirar desperdício de tempo.\n\n**Recomendação direta:** só mexa nessas opções se:\n\n- o modelo começar a repetir palavras (loop)\n- estiver errando demais em blocos grandes\n\nSe for só por causa de uma ou duas palavras... aceita e segue. Ou então faz\nigual eu: **testa tudo por uma semana e conclui que o padrão já era bom** 😅\n\n**Exemplo:**\n\nO arquivo de saída será `srt` (SubRip) na pasta indicada em `-o`. Essa pasta\nserá criada caso não exista.\n\n```bash\n# Greedy: Mais rápido, mas pode errar mais por considerar apenas uma hipótese por vez.\nwhisper /caminho/do/seu/arquivo.mp4 --temperature 0.0 --beam_size 1\n\n# Beam Search: Utiliza 3 hipóteses em paralelo.\n# O 'patience' padrão é 1.\nwhisper /caminho/do/seu/arquivo.mp4 --temperature 0.0 --beam_size 3\n\n# Sampling: Gera 5 amostras diferentes para escolher a melhor.\nwhisper /caminho/do/seu/arquivo.mp4 --temperature 0.7 --best_of 5\n```\n\n---\n\n**`--temperature_increment_on_fallback`**: Este argumento permite que você\n**aumente a temperatura do modelo em casos de falha na transcrição**. Se o\nmodelo encontrar dificuldades na temperatura `0.0`, ele fará um \"fallback\" e\ntentará com a temperatura incrementada. O valor também varia de `0.0` a `1.0`.\nNo entanto, **cuidado: definir `0.0` para este argumento causará um erro\n`ZeroDivisionError: float division by zero`** (isso pode ser um pequeno\n\"bugzinho\" 🫣, mas, de fato, não faria muito sentido usar zero aqui, já que o\nobjetivo é justamente _incrementar_ a temperatura). O valor padrão é `0.2`.\n\n---\n\n**`--max_line_width`**: Define a **quantidade máxima de caracteres por linha**\nna sua legenda. O valor padrão é `1000` (um limite bastante alto, codificado\ndiretamente na classe `SubtitlesWriter` do `whisper`). Eu, particularmente,\ncostumo usar `45` para uma melhor legibilidade. **Importante:** Se este\nargumento for utilizado, ele anula o `--max_words_per_line`. **Requer\n`--word_timestamps True`**.\n\n---\n\n**`--max_line_count`**: Controla a **quantidade máxima de linhas por legenda**\n(ou \"bloco\" de texto). Eu uso o valor `2`, mas, nos meus testes, percebi que\nisso força todas as legendas a terem sempre duas linhas. Para mim, não é um\nproblema, mas vale a pena você testar para ver como se adapta ao seu caso.\n**Requer `--word_timestamps True`**.\n\n---\n\n**`--max_words_per_line`**: Determina a **quantidade máxima de palavras por\nlinha** na legenda. O padrão também é um valor alto, `1000` (também \"hardcoded\"\nna classe `SubtitlesWriter`). Embora eu não costume usá-lo, acredito que `5`\npalavras por linha pode resultar em uma leitura mais confortável. **Atenção:**\nSerá anulado por `--max_line_width` caso você use ambos no mesmo comando.\n**Requer `--word_timestamps True`**.\n\n---\n\n**`--highlight_words`**: Este é o argumento responsável por criar o **efeito de\n\"karaokê\"** na sua transcrição. Ele faz com que cada palavra falada seja\nsublinhada no momento exato em que é pronunciada. **Requer\n`--word_timestamps True`**.\n\n---\n\n**`--word_timestamps`**: Este argumento é a **chave** para ativar os recursos de\nsincronização detalhada. Ao defini-lo como `True`, o modelo passará a gerar\n**timestamps para cada palavra**, em vez de apenas por blocos de frase. Isso\npode, sim, aumentar consideravelmente o tempo de transcrição, mas é um requisito\nfundamental para que vários outros argumentos (como os de formatação de linha e\ndestaque de palavras) funcionem. O valor padrão é `False`.\n\n---\n\n**Exemplo Completo de Transcrição Detalhada**\n\nVeja um exemplo de como combinar vários desses argumentos para obter uma\ntranscrição formatada e com destaque de palavras:\n\n```bash\n# A '\\' (barra invertida no final da linha) é usada apenas para indicar que\n# o comando continua na linha de baixo. Isso é uma boa prática para evitar\n# que o comando fique muito longo na horizontal e melhora a legibilidade.\nwhisper meu_video.mp4 \\\n  --model large-v2 \\\n  --language pt \\\n  --output_format srt \\\n  --word_timestamps True \\\n  --highlight_words True \\\n  --max_line_width 45 \\\n  --max_line_count 2\n```\n\n---\n\n**`--initial_prompt`**:\n\nEste é um texto opcional que serve como um **\"empurrãozinho\" para o modelo antes\nque ele comece a transcrever**. Funciona como uma dica de estilo ou contexto. No\nentanto, é importante notar que ele só influencia a **primeira \"janela\" do\náudio** (que por padrão tem 30 segundos).\n\n**Exemplo Prático:**\n\nSe o seu vídeo é sobre programação, especificamente Python, você pode passar um\nprompt como este:\n\n```bash\n--initial_prompt \"vídeo de uma explicação sobre programação com destaque para bibliotecas do Python\"\n```\n\nIsso pode ajudar o modelo a reconhecer e transcrever termos técnicos de\nprogramação e Python com mais precisão. Mas, como dito, não espere milagres para\no vídeo inteiro; essa influência é apenas um toque inicial. Para as janelas\nseguintes, o modelo pode usar o texto transcrito anteriormente, se a opção\n`--condition_on_previous_text` estiver como `True` (que é o padrão).\n\n**Analogia para Entender Melhor:**\n\n\u003e Imagine que é como dizer para um cantor, antes de ele subir no palco: \\\n\u003e \"Tem 300 mil pessoas te esperando, detona lá!\" \\\n\u003e Ele vai subir já no clima certo, mas o resto da performance dependerá do show\n\u003e em si. \\\n\u003e Da mesma forma, o modelo continua a transcrição com base no que \"ouviu\" e\n\u003e transcreveu depois do prompt inicial.\n\n**Cuidados com o `--initial_prompt`**:\n\nO `--initial_prompt` pode afetar significativamente a forma como o modelo do\n`whisper` opera. Em alguns casos, ele pode levar à geração de legendas\nexcessivamente longas ou até mesmo fazer o modelo entrar em **loops de\nrepetição**.\n\n**Recomendação:** Antes de aplicar um prompt em um vídeo completo, faça **testes\nrápidos em um trecho menor** do seu vídeo para observar o resultado. Isso evita\nsurpresas e economiza tempo de processamento.\n\nPara cortar facilmente um pedaço do seu vídeo para testes, você pode usar o\n`ffmpeg` com o seguinte comando:\n\n```bash\n# Com ffmpeg\nffmpeg -i entrada.mp4 -c:v copy -c:a copy -ss 00:05:00.000 -to 00:10:00.000 saida.mp4\n\n# Também dá pra usar --clip_timestamps start, end, start, end... (em segundos)\n# O argumento --clip_timestamps é detalhado mais abaixo nesse texto\n# No comando abaixo ele transcreve de 1min até 2min (nada mais)\nwhisper meu_video.mp4 --clip_timestamps 60,120\n```\n\n**Entendendo o Comando `ffmpeg`:**\n\n- `-i entrada.mp4`: Define o arquivo de vídeo de entrada (o seu vídeo original).\n- `-c:v copy`: Copia o codec de vídeo do arquivo original, sem recodificar. Isso\n  torna o processo muito mais rápido!\n- `-c:a copy`: Copia o codec de áudio do arquivo original, também sem\n  recodificar.\n- `-ss 00:05:00.000`: Especifica o ponto de início do corte (neste exemplo, 5\n  minutos e 0 segundos do vídeo original).\n- `-to 00:10:00.000`: Define o ponto final do corte (neste exemplo, 10 minutos e\n  0 segundos do vídeo original).\n\nEste comando irá gerar um novo arquivo de vídeo (`saida.mp4`) contendo apenas o\nsegmento entre 00:05:00 e 00:10:00 do vídeo original. Essa técnica é\nextremamente útil, especialmente para vídeos mais longos (como os meus de 30+\nminutos), pois permite testar configurações específicas em um pedaço pequeno sem\nter que processar o vídeo inteiro.\n\n---\n\n**`--condition_on_previous_text`**:\n\nEste argumento crucial define se **o texto que já foi transcrito será usado como\ncontexto** para ajudar a transcrever a próxima \"janela\" do áudio.\n\n- `True` (padrão): É a configuração ideal para a maioria dos casos. Ela ajuda a\n  manter a **fluidez e a consistência** do texto, garantindo uma boa coesão\n  entre os blocos da transcrição.\n- `False`: Desativa o uso do contexto anterior. Isso pode ser útil para **evitar\n  \"loops de erro\"**, onde o modelo fica repetindo frases ou palavras\n  indefinidamente.\n\n\u003e _Exemplo de Uso:_ \\\n\u003e Se a transcrição começar a errar e ficar repetindo, por exemplo,\n\u003e `\"Olá, pessoal, hoje vamos falar sobre...\"` em loop, desativar este argumento\n\u003e (`--condition_on_previous_text=False`) pode quebrar esse ciclo vicioso.\n\n---\n\n**Recomendações Gerais para Contexto**\n\nPara otimizar suas transcrições, considere as seguintes dicas:\n\n- Para vídeos **bem gravados**, com **áudio limpo** e **sem erros ou repetições\n  evidentes**, mantenha o padrão: `--condition_on_previous_text=True`.\n- Se o modelo começar a **repetir frases ou palavras** de forma indesejada,\n  experimente mudar para `--condition_on_previous_text=False`.\n- O `--initial_prompt` pode ajudar **somente no início** da transcrição. Não\n  espere que ele resolva problemas de consistência para o vídeo inteiro, mas\n  pode ser útil para guiar o modelo em termos específicos.\n\n---\n\n### Parâmetros que não usei (ou quase não usei 🫣):\n\nEsses parâmetros aí de baixo **eu não testei quase nada** (apenas alguns). Só li\na documentação, pesquei uma ideia geral e traduzi pra você não precisar sofrer.\nSe quiser fuçar, fuce, mas vai por sua conta e risco. Pode ser que melhore algo,\npode ser que não mude nada. Vai depender do áudio, da fase da lua e do humor do\nmodelo 😅.\n\nSe eu começar a usar alguma dessas opções nas minhas transcrições, prometo que\nvolto aqui e atualizo esse trecho. Alguns deles eu cheguei a testar de forma\nsupercifical (explico nos argumentos).\n\n---\n\n**`--length_penalty`**\n\nControla a penalização para _sequências longas_. Valor típico: entre `0.6` e\n`1.0`. Se você notar que a transcrição tá muito curta ou longa, pode brincar com\nisso. Eu não toquei neste argumento.\n\n---\n\n**`--suppress_tokens`**\n\nPermite suprimir tokens pelo ID. O valor `-1` (padrão) já suprime símbolos\nesquisitos e só mantém pontuações comuns. Deixa assim, a menos que você saiba o\nque está fazendo.\n\nExemplo:\n\n```sh\n# Isso aqui vai cortar algumas coisas úteis (só exemplo).\n# Seu texto não terá: 'Olá', 'pessoal', ',', 'este', 'é', 'meu', 'texto', '.'\n# Obs: texto sem ponto e vírgula fica horrível\nwhisper /caminho/do/seu/arquivo.mp4 \\\n    --model turbo \\\n    --language pt \\\n    --suppress_tokens=38056,842,24811,11,4065,1136,9230,35503,13\n```\n\n_Quer saber o ID de um token específico?_\n\nSeguinte, se você que descobrir algum token para suprimir ou para qualquer outra\ncoisa, veja um exemplo:\n\n```python\n\u003e\u003e\u003e from whisper.tokenizer import get_tokenizer\n# get_tokenizer -\u003e multilingual, num_languages=99, language='pt', task='transcribe'\n#                  True,         99                Qual idioma    Qual task\n\u003e\u003e\u003e tokenizer = get_tokenizer(True, num_languages=99, language='pt', task='transcribe')\n# tokenizer.encode você passa o 'valor' e recebe os tokens List[int]\n\u003e\u003e\u003e tokenizer.encode('Olá pessoal, este é meu texto.')\n[38056, 842, 24811, 11, 4065, 1136, 9230, 35503, 13]\n# tokenizer.decode você passa os tokens List[int] e recebe o 'valor'\n\u003e\u003e\u003e tokenizer.decode([38056, 842, 24811, 11, 4065, 1136, 9230, 35503, 13])\n'Olá pessoal, este é meu texto.'\n\u003e\u003e\u003e\n```\n\n---\n\n**`--fp16`**\n\nUsa precisão _float16_ para acelerar em GPU.\n\nNo Mac M1, por exemplo, eu sempre uso `--fp16 False`, assim ele não fica\nmostrando warning de que trocou pra _float32_. Essa troca acontece\nautomaticamente se o seu hardware **não** suportar _float16_, então:\n\n- **Se suportar:** passa direto com _float16_.\n- **Se não suportar:** ele mostra um aviso e troca para _float32_ sozinho.\n\nExemplo do warning:\n\n```\nFP16 is not supported on CPU; using FP32 instead\n```\n\n---\n\n**`--compression_ratio_threshold`**\n\nSe a razão de compressão (gzip) do texto for muito alta, ele assume que houve\nerro (textos muito repetitivos). Valor padrão é `2.4`. Útil pra detectar _loop\nde repetição_.\n\n**Como funciona a ideia:**\n\nO Whisper pega o texto, compacta com gzip e compara o **tamanho original** com o\n**tamanho compactado** para calcular a **razão de compressão**. Textos\nrepetitivos geram compressões mais eficientes, ou seja, **razão mais alta**.\n\n- `\"Olá, olá, olá, olá, olá...\"` → compacta muito → **alta razão**\n- `\"O rato roeu a roupa do rei de Roma.\"` → mais diversidade → **menor razão**\n\nSe a razão ultrapassar o limite definido (padrão: `2.4`), o Whisper **descarta o\ntrecho** por considerá-lo problemático (repetitivo, bugado etc).\n\nSe sua transcrição estiver falhando **sem motivo claro**, esse filtro pode ser o\nculpado. Teste com `--compression_ratio_threshold 0` e veja se melhora.\n\n---\n\n**`--logprob_threshold`**\n\nSe a média do logaritmo da probabilidade (logprob) dos tokens estiver abaixo\ndisso, ele trata como erro. Padrão: `-1.0`. Você consegue ver `avg_logprob`\n(média do logaritmo da probabilidade) das frases transcritas pelo Whisper no\narquivo `.json` final gerado. Este arquivo contém algo similar a isso:\n\n```json\n{\n  \"id\": 102,\n  \"seek\": 27632,\n  \"start\": 293.8,\n  \"end\": 294.66,\n  \"text\": \" primeiro os imports\",\n  \"tokens\": [51240, 18314, 3003, 41596, 51283],\n  \"temperature\": 0.0,\n  \"avg_logprob\": -0.08265516709308235,\n  \"compression_ratio\": 1.7523364485981308,\n  \"no_speech_prob\": 1.1688144375965326e-11,\n  \"words\": [\n    {\n      \"word\": \" primeiro\",\n      \"start\": 293.8,\n      \"end\": 294.06,\n      \"probability\": 0.7435079216957092\n    },\n    {\n      \"word\": \" os\",\n      \"start\": 294.06,\n      \"end\": 294.24,\n      \"probability\": 0.9965941309928894\n    },\n    {\n      \"word\": \" imports\",\n      \"start\": 294.24,\n      \"end\": 294.66,\n      \"probability\": 0.990346372127533\n    }\n  ]\n}\n```\n\nAqui `avg_logprob` é `-0.08265516709308235`. Quanto mais próximo de `0`, mais\nconfiante está o modelo.\n\nSuponha que o modelo está descartando coisas na transcrição. Você poderia testar\n`--logprob_threshold=-2.0` ou até `--logprob_threshold=-1000` (não descarta\nnada).\n\nIsso pode gerar muito ruído aleatório na transcrição, mas pode fazer ele\ndetectar o que você quer.\n\nO contrário também é verdadeiro. Se usar `--logprob_threshold=-0.1` (por\nexemplo), o modelo vai pegar praticamente só o que tem certeza absoluta que tá\ncerto. Isso não é uma coisa boa ou ruim, depende do contexto e do seu objetivo.\nNa dúvida, manter o padrão costuma ser uma escolha segura.\n\nQuanto mais rigoroso, mais lento, porque ele vai tentar gerar várias hipóteses\naté alcançar esse nível de confiança. No fim das contas, ele vai te entregar um\ntexto de qualquer jeito, mas pode demorar bem mais e talvez nem seja tão\ndiferente assim.\n\n---\n\n**`--no_speech_threshold`**\n\nSe o modelo acredita que é silêncio (probabilidade alta de `\u003c|nospeech|\u003e`) **e**\na decodificação falha (`logprob_threshold`), ele descarta o trecho como sendo\nsilêncio. Isso ajuda a cortar \"respiros vazios\" da transcrição.\n\nEssa funcionalidade me encanta, e tenho planos futuros pra ela. Quem sabe a\ngente não volta a falar disso mais pra frente?\n\n---\n\n**`--prepend_punctuations`** (com `--word_timestamps True`):\n\nEste argumento controla quais caracteres de pontuação que aparecem **antes** de\numa palavra devem ser \"colados\" à palavra seguinte, em vez de serem tratados\ncomo um token separado.\n\n- **Padrão**: `\\\"\\'“¿([{-` (inclui aspas, parênteses, etc.) e requer\n  `--word_timestamps True`.\n\nEm teoria, se o modelo gerasse, por exemplo, os tokens `(`, `arg`, `ument`,\n`os`, `)` separadamente (tipo: `[7, 33544, 2206, 329, 8]` que formariam\n`(argumentos)`), o `(` e o `arg` seriam unidos para formar `(arg`.\n\n\u003e **Observação Importante**: eu testei o `whisper` com os idiomas `Portuguese` e\n\u003e `English` (90% em `Portuguese`, que é meu caso de uso). Em nenhuma das\n\u003e legendas que gerei houve qualquer caso onde a pontuação viesse antes de alguma\n\u003e palavra. Na prática, eu realmente não usei este parâmetro.\n\n---\n\n**`--append_punctuations`** (com `--word_timestamps True`):\n\nEste argumento controla quais caracteres de pontuação que aparecem **depois** de\numa palavra devem ser \"colados\" à palavra anterior.\n\n- **Padrão**: `\\\"\\'.。,，!！?？:：”)]}、` (inclui aspas, pontos, vírgulas,\n  interrogações, etc.) e requer `--word_timestamps True`.\n\nPor exemplo, se os tokens gerados forem `Ok` e `?` separadamente, e o `?`\nestiver incluído nesta lista (o que já está por padrão), eles serão unidos para\nformar `Ok?`.\n\n**Dica Prática**: Esses argumentos de pontuação só farão uma diferença\nperceptível se você precisar que o ponto ou outro símbolo tenha um `timestamp`\n_exatamente_ separado da palavra, o que é um caso de uso bastante específico. Na\nmaioria das situações, o padrão do `whisper` já é bastante robusto. Do\ncontrário, e para simplificar, mantenha os valores padrão.\n\n---\n\n### Outros úteis\n\n**`--threads`**\n\nDefine o número de _threads_ que o modelo vai usar na CPU. Exemplo:\n`--threads 4`. Se não passar nada, ele usa o padrão da Torch (geralmente via MKL\nou OMP).\n\nNos meus testes (Mac M1), usei `1, 4, 10, 100, 1000`. O resultado? Ele só criou\nmais _threads_ e usou mais CPU, **mas a velocidade de transcrição não mudou\nabsolutamente nada**.\n\nClaro, meus testes foram superficiais. Pode ser que em outro sistema, com outra\nCPU (ou invocando Cthulhu no terminal), você veja alguma diferença. Eu? Só vi o\ncooler suando.\n\n---\n\n**`--clip_timestamps`**\n\nPermite transcrever ou traduzir apenas trechos específicos do áudio ou vídeo.\nVocê passa os intervalos como pares `start,end` (em segundos). Pode usar vários.\n\n**Exemplos:**\n\n- `--clip_timestamps 10,30` → transcreve de 10s até 30s\n- `--clip_timestamps 60,120` → de 1min até 2min\n- `--clip_timestamps 10,30,60,120` → dois trechos: 10s–30s e 1min–2min\n- ⚠️ `--clip_timestamps 270` → de 4min30s até o final\n- ⚠️ `--clip_timestamps 60,120,0` → transcreve de 1min–2min **e depois recomeça\n  do zero até o fim**\n\n**Atenção:**\n\nEsse último exemplo (`60,120,0`) parece um caso não previsto.\n\nO `0` vem depois de `120`, mas não forma um par `start,end`.\n\nNos testes, isso gerou um comportamento curioso: o modelo transcreveu\nnormalmente de 1min até 2min, **e depois do início até o final**.\n\nMesmo assim, o VLC interpretou direitinho. Ele realinhou os blocos e ignorou os\nduplicados, mostrando só o que fazia sentido cronológico (**aparentemente\ncortando o primeiro minuto**).\n\n---\n\n**`--hallucination_silence_threshold`**\n\nFunciona junto com `--word_timestamps True`.\n\nEle tenta detectar trechos de silêncio longos que o modelo pode ter \"alucinado\"\n(inventado texto).\n\nSe você passar `--hallucination_silence_threshold 1.5`, ele vai **ignorar\nsilêncios maiores que 1.5s que geraram texto suspeito**. Não toquei nesse\nargumento.\n\n---\n\n## Usando o Whisper via código\n\nPara usar o `whisper` via código, é bem simples. Como informado no repositório\ndeles, basta usar o seguinte para uso normal do whisper.\n\n```python\nimport whisper\n\nmodel = whisper.load_model(\"turbo\")\nresult = model.transcribe(\"audio.mp3\")\nprint(result[\"text\"])\n```\n\nPara um acesso de mais baixo nível:\n\n```python\nimport whisper\n\nmodel = whisper.load_model(\"turbo\")\n\n# load audio and pad/trim it to fit 30 seconds\naudio = whisper.load_audio(\"audio.mp3\")\naudio = whisper.pad_or_trim(audio)\n\n# make log-Mel spectrogram and move to the same device as the model\nmel = whisper.log_mel_spectrogram(audio, n_mels=model.dims.n_mels).to(model.device)\n\n# detect the spoken language\n_, probs = model.detect_language(mel)\nprint(f\"Detected language: {max(probs, key=probs.get)}\")\n\n# decode the audio\noptions = whisper.DecodingOptions()\nresult = whisper.decode(model, mel, options)\n\n# print the recognized text\nprint(result.text)\n```\n\n### E como eu fiz meu código?\n\nEu fiz o código de uma forma que eu continuasse usando todos os parâmetros do\n`whisper`, porém, adicionando minha própria lógica.\n\nBasicamente eu simulo que os argumentos estão sendo enviados para mim com\n`sys.argv` do Python. No Diagrama abaixo eu mostro o processo do \"terminal\" até\nchegar ao `argparse`, e do lado direito, como montei meu código também chamando\no argparse.\n\n\u003cp\u003e\n    \u003ca\n        href=\"images/diagrama.webp\"\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n    \u003e\n        \u003cimg\n            src=\"images/diagrama.webp\"\n            alt=\"Diagrama exibindo como usei o `sys.argv` para simular o terminal no meu código\"\n        /\u003e\n        \u003cem\n            \u003eDiagrama exibindo como usei o `sys.argv` para simular o terminal no meu código\u003c/em\n        \u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\nVeja o código a seguir. Só para constar, tem um logger em outro módulo com o\nseguinte código.\n\n```python\nimport logging\nimport os\n\nfrom rich.logging import RichHandler\n\nlevel_name = os.getenv(\"SUSSU_LOG_LEVEL\", \"INFO\").upper()\nlevel = getattr(logging, level_name, logging.INFO)\n\nlogging.basicConfig(\n    level=level,\n    format=\"%(message)s\",\n    datefmt=\"[%H:%M]\",\n    handlers=[\n        RichHandler(\n            show_time=True,\n            show_level=True,\n            rich_tracebacks=True,\n            omit_repeated_times=False,\n            markup=False,\n        )\n    ],\n)\n\nlogger = logging.getLogger(\"rich\")\n```\n\nDefina a variável de ambiente `SUSSU_LOG_LEVEL` para controlar o nível de log.\nSe nenhuma for definida, o padrão será `INFO`.\n\nAgora sim, vamos ver o código. Deixei vários comentário explicando tudo.\n\n```python\nimport argparse\nfrom pathlib import Path\n\nimport rich_argparse\n\nfrom sussu.basic_logger import logger\n\n# Example commands:\n#\n# sussu whisper ~/Desktop/videos/part_0004.mp4 --temperature 0 --beam_size 1 \\\n# --device cpu --fp16 False --output_format srt --model tiny --language pt \\\n# --output_dir ~/Desktop/videos/\n#\n# sussu one ~/Desktop/videos/part_0004.mp4 --temperature 0 --beam_size 1 \\\n# --device cpu --fp16 False --output_format srt --model tiny --language pt \\\n# --output_dir ~/Desktop/videos/\n#\n# sussu batch --input_dir ~/Desktop/videos/ --temperature 0 --beam_size 1\n# --device cpu --fp16 False --output_format srt --model tiny --language pt\n# -s video.mp4 part_0000.mp4 --skip_files part_0001.mp4\n# --output_dir 'this wont do anything here'\n\n\n# Essa função é basicamente um jeito de \"enganar\" o cli do `whisper`\n# para que ele \"entenda\" que está sendo chamado com determinados argumentos.\ndef whisper_cli_runner(whisper_args: list[str]) -\u003e None:\n    import sys\n\n    # `whisper` não tem stub, por isso o pyright vai gerar erro (ignorado)\n    from whisper.transcribe import cli as whisper_cli  # pyright: ignore\n\n    # Aqui está a malícia. Vamos fingir que o python está recebendo os argumento\n    # via sys.argv. Com isso o argparse entra em ação da mesma forma que\n    # entraria se estivesse sendo executado via linha de comando.\n    sys.argv = [\"whisper\", *whisper_args]\n    whisper_cli()\n\n\n# Essa é a nossa função que vai processar os arquivos usando o whisper original\ndef batch_whisper(\n    input_dir: Path, whisper_raw_args: list[str], skip_files: list[str] | None = None\n) -\u003e None:\n    # Vamos preencher essa lista com os dados que precisamos\n    whisper_args: list[str] = []\n\n    # As extensões abaixo podem não conter todas as extensões suportadas pelo\n    # ffmpeg, sinta-se à vontade para adicionar novas extensões\n    # fmt: off\n    allowed_extensions =  {\n        \".mp3\", \".wav\", \".flac\", \".aac\", \".m4a\", \".ogg\", \".opus\", \".mp4\", \".mkv\",\n        \".webm\", \".mov\", \".avi\", \".3gp\", \".wmv\",\n    }\n    # fmt: on\n\n    # Às vezes tem alguns arquivos na mesma pasta que são válidos, mas não\n    # queremos transcrever (eu só queria agilizar meus testes manuais)\n    if not skip_files:\n        skip_files = []\n\n    # Passamos em todos os arquivos da pasta enviada pelo usuário\n    for file in input_dir.iterdir():\n        skip_loop = False\n        ########## VAMOS PULAR ALGUNS ARQUIVOS PARA EVITAR ERROS ##########\n\n        # Pulamos quando é um subdiretório\n        if file.is_dir():\n            logger.warning(f\"Directory not allowed: {file.name}\")\n            continue\n\n        # Pulamos se a extensão não for permitida\n        if file.suffix not in allowed_extensions:\n            logger.error(f\"File extension not allowed: {file.name}\")\n            continue\n\n        # Pulamos também quando o usuário pede para pular aquele arquivo via -s\n        for skip_file in skip_files:\n            if str(file).endswith(skip_file):\n                logger.info(f\"File skipped: {file.name}\")\n                skip_loop = True\n\n        if skip_loop:\n            skip_loop = False\n            continue\n\n        ############ DAQUI EM DIANTE VAI PARA O WHISPER ##########\n\n        # O argumento posicional vai sozinho no primeiro índice\n        # depois os argumentos desconhecidos\n        whisper_args.extend([str(file), *whisper_raw_args])\n        logger.debug(f\"audio set as {file!s}\")\n\n        # Por fim, adicionamos o outdir para ser sempre a pasta onde está\n        # o arquivo original. Isso gera um arquivo de mesmo nome com a extensão\n        # `.srt`.\n        logger.debug(f\"--output_dir set to {file.parent}\")\n        whisper_args.extend([\"--output_dir\", str(file.parent)])\n\n        # Desativa o modo verboso do `whisper` por padrão para que a gente possa\n        # ver nossos logs. Se o user passar algo, usa o que ele passar.\n        if \"--verbose\" not in whisper_args:\n            whisper_args += [\"--verbose\", \"False\"]\n            logger.debug(\"--verbose set to False by default\")\n\n        # Agora só chamar o whisper com os argumentos que montamos\n        logger.debug(f\"whisper commands are: {whisper_args}\")\n        logger.debug(f\"Final command: whisper {' '.join(whisper_args)}\")\n        whisper_cli_runner(whisper_args)\n\n        # Zeramos os argumentos para o próximo loop\n        whisper_args = []\n\n\ndef build_argparse() -\u003e argparse.ArgumentParser:\n    # Nosso main parser e o subparser para os comandos\n    parser = argparse.ArgumentParser(\n        prog=\"sussu\", formatter_class=rich_argparse.RawDescriptionRichHelpFormatter\n    )\n    subparsers = parser.add_subparsers(dest=\"command\", required=True)\n\n    ########## WHISPER PARSER ##########\n\n    # Esse subparse só será usado como um wrapper do argparse do whisper.\n    # No final das contas, ele só vai chamar `whisper.transcribe.cli()`\n    whisper_parser = subparsers.add_parser(\n        \"whisper\",\n        help=\"Calls `whisper` directly\",\n        conflict_handler=\"resolve\",\n        aliases=[\"one\"],\n        formatter_class=rich_argparse.RawDescriptionRichHelpFormatter,\n    )\n    whisper_parser.set_defaults(command=\"whisper\")\n\n    # Esse argumento aqui é pra garantir que vamos chamar o help do whisper e\n    # não do nosso parser\n    whisper_parser.add_argument(\n        \"-h\", \"--help\", help=\"Shows `whisper` help.\", action=\"store_true\"\n    )\n\n    ########## NOSSOS PARSERS ##########\n    # Minha ideia aqui é criar um subparser `batch` que vai receber um diretório\n    # com arquivos de vídeo. Vamos passar em todos os arquivos do diretório e\n    # usar o whisper para transcrever cada um deles.\n\n    batch_parser = subparsers.add_parser(\n        \"batch\",\n        help=\"Process files with `whisper` in batch mode\",\n        formatter_class=rich_argparse.RawDescriptionRichHelpFormatter,\n    )\n\n    # Só coloquei essa função aqui para ficar próxima do argumento e facilitar\n    # minha explicação na hora de gravar.\n    def parse_input_dir(path_str: str) -\u003e Path:\n        path = Path(path_str)\n\n        if not path.is_dir():\n            msg = f\"{path_str!r} is not a directory\"\n            raise argparse.ArgumentTypeError(msg)\n\n        return path.resolve()\n\n    # Isso deverá ser uma pasta que contém arquivos de vídeo ou áudio\n    batch_parser.add_argument(\n        \"--input_dir\",\n        help=\"Directory with files to work with\",\n        type=parse_input_dir,\n        required=True,\n    )\n\n    # Para testar, eu estava pulando um monte de arquivos para ir mais rápido\n    batch_parser.add_argument(\n        \"-s\",\n        \"--skip_files\",\n        help=\"Name of file(s) to skip\",\n        action=\"extend\",\n        nargs=\"+\",\n        default=[],\n    )\n\n    # Essa foi a maneira mais simples e direta de remover output_dir dos\n    # unknown_args. Se isso fosse para o whisper, geraria conflito\n    batch_parser.add_argument(\"-o\", \"--output_dir\", help=argparse.SUPPRESS)\n    return parser\n\n\ndef run() -\u003e None:\n    ########## PARSE KNOWN ARGS ##########\n\n    # Vamos receber argumentos que são conhecidos (os nossos), e desconhecidos.\n    # Argumentos desconhecidos serão repassados para o whisper cli.\n    parser = build_argparse()\n    args, unknown_args = parser.parse_known_args()\n\n    # Se o comando for whisper, passamos tudo direto para o whisper\n    if args.command == \"whisper\":\n        # Simula -h e --help\n        if args.help:\n            whisper_cli_runner([\"--help\"])\n            return\n\n        # Executa o whisper normal, só que por baixo de `sussu`\n        # Ex.: `sussu whisper audio.mp3` chama o cli original do `whisper` com\n        # o argumento `audio.mp3` (ou qualquer outro argumento)\n        whisper_cli_runner(unknown_args)\n        return\n\n    # Se o comando for `batch`, fazemos nosso trabalho\n    if args.command == \"batch\":\n        batch_whisper(args.input_dir, unknown_args, args.skip_files)\n\n\nif __name__ == \"__main__\":\n    run()\n```\n\nÉ só isso! Obrigado por ler.\n\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluizomf%2Fsussu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fluizomf%2Fsussu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluizomf%2Fsussu/lists"}