yt-zmp 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## 1.1.0
4
+
5
+ ### Adicionado
6
+ - Suporte a proxy HTTP/HTTPS (`proxy` por chamada, `setDefaultProxy` e `YOUTUBE_PROXY`) em todas as requisições: página, Player API, JavaScript do player e download do stream. Usa `undici` (dependência opcional).
7
+ - Bridge para servidores com IP bloqueado: servidor `yt-zmp-bridge` e cliente `bridgeDownload` (`yt-zmp/bridge`).
8
+ - Detecção de travamento no download (`stallTimeoutMs`, padrão 30 s): o stream que para de enviar dados é abortado e a tentativa é repetida, em vez de ficar pendurado.
9
+ - Linha de comando `yt-zmp`.
10
+ - Suíte de testes offline (`npm test`).
11
+
12
+ ### Corrigido
13
+ - O extrator descarta respostas sem formatos utilizáveis (sem `streamingData`, só SABR ou sem `url`/`signatureCipher`) e passa para o próximo cliente, em vez de parar no primeiro que devolve `streamingData` vazio.
14
+ - `itag`, `bitrate`, `width`, `height` e `fps` são normalizados para número, pois alguns retornos os trazem como texto e quebravam a comparação por `itag`.
15
+ - Mensagens de erro mais claras: status de reprodução do cliente que falhou e causa do erro de conexão no bridge.
16
+
17
+ ### Alterado
18
+ - Código-fonte movido para `src/`; o ponto de entrada continua sendo `import YouTube from 'yt-zmp'`.
19
+ - Mínimo de Node.js: 18.17.
20
+ - O pacote publica apenas `index.js`, `src/`, `bin/`, `README.md`, `LICENSE` e `CHANGELOG.md`.
21
+
22
+ ## 1.0.0
23
+
24
+ Versão inicial.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 yt-zmp contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,132 +1,264 @@
1
- # YouTube Media Downloader (experimental)
1
+ # yt-zmp
2
2
 
3
- Módulo Node.js em fase de testes para consultar vídeos do YouTube e baixar uma mídia a partir dos streams que o YouTube disponibilizar.
3
+ Extração e download de áudio e vídeo do YouTube em Node.js, **sem yt-dlp**. Consulta os clientes internos do YouTube, decifra as URLs dos streams e baixa a mídia, com conversão para MP3 e junção de vídeo e áudio via FFmpeg.
4
4
 
5
- O download é implementado pelo próprio módulo, sem usar `yt-dlp`. O extrator tenta clientes internos do YouTube (`ANDROID`, `WEB`, `MWEB`, `IOS` e `TVHTML5`) e usa o perfil que retornar os streams. Os perfis Android e iOS usam versões e parâmetros de dispositivo compatíveis com os clientes atuais observados no código do yt-dlp.
5
+ Recursos principais:
6
6
 
7
- Para áudio, o seletor prioriza `audio/mp4` (AAC, geralmente itag 140) antes de WebM/Opus, pois esse stream é mais compatível com download direto e conversão para MP3.
7
+ - Cadeia de clientes (`WEB`, `MWEB`, `ANDROID`, `IOS`, `TVHTML5` e outros), usando o primeiro que devolver streams utilizáveis.
8
+ - Áudio (`m4a` ou `mp3`), vídeo em qualidade escolhida (com junção de áudio) e streams muxados (ex.: itag 18).
9
+ - **Proxy HTTP/HTTPS** por chamada, por variável de ambiente ou global.
10
+ - **Bridge** para servidores cujo IP o YouTube bloqueia: o download é feito em outra máquina com IP residencial.
11
+ - Cookies (formato Netscape) e GVS PO token opcionais.
12
+ - Download com detecção de travamento: se o stream parar de enviar dados, a tentativa é abortada e repetida.
8
13
 
9
- Se um formato retornar 403, o módulo tenta os demais streams de áudio disponíveis e repete a requisição sem `Range` antes de renovar a extração. Os cookies de sessão continuam sendo usados somente nas requisições ao YouTube.
14
+ > Projeto independente, sem relação com o YouTube ou o Google. O YouTube muda seus endpoints com frequência; se algo parar de funcionar, atualize o pacote. Use apenas em conteúdo que você tem autorização para baixar e respeite os termos de uso do serviço.
10
15
 
11
- Quando o YouTube entrega `signatureCipher`, o módulo carrega o JavaScript do player em um sandbox isolado e reutiliza a rotina de assinatura do próprio player antes de montar a URL do stream. Há também um decifrador heurístico de fallback para versões mais simples do player.
12
-
13
- Para baixar o vídeo, combinar vídeo e áudio com FFmpeg e entregar somente o áudio final, use `video-audio`:
16
+ ## Instalação
14
17
 
15
18
  ```bash
16
- node download-test.js "URL_DO_YOUTUBE" video-audio 720 mp3
19
+ npm install yt-zmp
17
20
  ```
18
21
 
19
- Esse modo cria temporariamente um MP4 com vídeo e áudio, extrai o áudio com FFmpeg e remove o arquivo temporário. Use `m4a` para preservar o áudio AAC sem recodificação ou `mp3` para converter com LAME.
20
-
21
- ## Sessão autenticada opcional
22
+ - Node.js **18.17 ou superior**.
23
+ - **FFmpeg** no `PATH` (ou informe `ffmpegPath`) para gerar MP3, combinar vídeo com áudio ou extrair áudio de um stream muxado. O formato `m4a` direto não precisa de FFmpeg.
24
+ - O `undici` é instalado como dependência opcional e só é usado quando há proxy.
22
25
 
23
- Para vídeos que exigem uma conta autenticada, o módulo aceita um arquivo de cookies no formato Netscape. Use somente cookies da sua própria conta, mantenha o arquivo fora do repositório e nunca cole os valores em logs ou mensagens. O módulo usa os cookies e o `SAPISIDHASH` apenas nas requisições ao YouTube; eles não são enviados ao host `googlevideo.com`.
26
+ O pacote é ESM. Em projetos CommonJS, use `import()` dinâmico:
24
27
 
25
- ```bash
26
- YOUTUBE_COOKIE_FILE=/caminho/privado/youtube-cookies.txt \
27
- node download-test.js "https://www.youtube.com/watch?v=VIDEO_ID" audio 0 mp3
28
+ ```js
29
+ const { default: YouTube } = await import('yt-zmp');
28
30
  ```
29
31
 
30
- Pela API:
32
+ ## Uso rápido
31
33
 
32
34
  ```js
33
- await YouTube.downloadMedia(url, {
35
+ import YouTube from 'yt-zmp';
36
+
37
+ // Áudio em MP3
38
+ const audio = await YouTube.downloadMedia('https://youtu.be/VIDEO_ID', {
34
39
  kind: 'audio',
35
- audioFormat: 'm4a',
36
- cookieFile: '/caminho/privado/youtube-cookies.txt'
40
+ audioFormat: 'mp3',
41
+ outputDir: './downloads'
37
42
  });
38
- ```
43
+ console.log(audio.path, audio.title, audio.size);
39
44
 
40
- O arquivo deve conter linhas Netscape com domínio, caminho, expiração, nome e valor. Cookies autenticados não garantem a superação de todos os desafios anti-bot, CAPTCHA ou exigências de verificação do YouTube; nesses casos, o módulo retorna o motivo informado pelo serviço.
45
+ // Vídeo até 720p, com áudio combinado
46
+ const video = await YouTube.downloadMedia('https://youtu.be/VIDEO_ID', {
47
+ kind: 'video',
48
+ height: 720
49
+ });
41
50
 
42
- ## GVS PO token
51
+ // Só metadados e formatos, sem baixar
52
+ const info = await YouTube.extract('https://youtu.be/VIDEO_ID');
53
+ console.log(info.title, info.client.name, info.formats.length);
54
+ ```
43
55
 
44
- Versões recentes do YouTube podem exigir um **GVS PO token** para autorizar as URLs de mídia. O yt-zmp não tenta fabricar esse token nem usa o yt-dlp para obtê-lo. Quando você possui um token legítimo para a sessão/cliente, forneça-o por variável de ambiente; ele será acrescentado como `pot` às URLs de stream durante a extração:
56
+ A linha de comando faz o mesmo:
45
57
 
46
58
  ```bash
47
- YOUTUBE_GVS_PO_TOKEN='TOKEN_BASE64URL' \
48
- YOUTUBE_COOKIE_FILE=/caminho/privado/youtube-cookies.txt \
49
- node download-test.js "https://www.youtube.com/watch?v=VIDEO_ID" video-audio 720 mp3
59
+ npx yt-zmp "https://youtu.be/VIDEO_ID" audio 0 mp3
60
+ npx yt-zmp "https://youtu.be/VIDEO_ID" video 720
61
+ npx yt-zmp "https://youtu.be/VIDEO_ID" muxed
50
62
  ```
51
63
 
52
- Também é possível passar `poToken` diretamente em `extract`, `downloadMedia` ou `downloadAudioFromVideo`. O valor não deve ser salvo no repositório nem impresso em logs. Sem PO token, alguns formatos podem aparecer na extração, mas retornar HTTP 403 no host `googlevideo.com`; nesse caso trocar cabeçalhos ou repetir a mesma URL não resolve a autorização ausente.
64
+ Uso: `yt-zmp URL [audio|video|video-audio|muxed] [altura] [m4a|mp3]`. Lê `YOUTUBE_COOKIE_FILE`, `YOUTUBE_GVS_PO_TOKEN` e `YOUTUBE_PROXY` do ambiente.
53
65
 
54
- Para testar a sessão sem baixar mídia:
66
+ ## API
55
67
 
56
- ```bash
57
- YOUTUBE_COOKIE_FILE=/caminho/privado/youtube-cookies.txt \
58
- npm run auth:test -- "https://www.youtube.com/watch?v=VIDEO_ID"
59
- ```
68
+ ### `YouTube.downloadMedia(input, options)`
60
69
 
61
- O teste informa apenas se o arquivo foi lido, quantos cookies foram encontrados, se o `SAPISIDHASH` foi gerado e se o YouTube retornou metadados/formatos. Ele nunca imprime os valores dos cookies.
70
+ `input` aceita link (`watch`, `youtu.be`, `shorts`, `embed`) ou o ID de 11 caracteres. Devolve `{ path, size, id, title, kind, audioFormat, format }`.
62
71
 
63
- ## Uso rápido
72
+ | Opção | Padrão | Descrição |
73
+ | --- | --- | --- |
74
+ | `kind` | `'muxed'` | `'audio'`, `'video'` ou `'muxed'` (vídeo com áudio no mesmo stream, em geral até 360p). |
75
+ | `audioFormat` | `'m4a'` | Para `kind: 'audio'`: `'m4a'` ou `'mp3'`. |
76
+ | `mp3Quality` | `'2'` | Qualidade LAME (0 melhor, 9 pior). |
77
+ | `height` | — | Altura máxima para vídeo. Se não existir, usa a melhor abaixo do limite. |
78
+ | `itag` | — | Força um itag específico. |
79
+ | `mergeAudio` | `true` | Em `kind: 'video'`, baixa o melhor áudio e combina com FFmpeg. |
80
+ | `outputDir` | `'./downloads'` | Pasta de saída. |
81
+ | `outputPath` | — | Caminho final do arquivo (ignora `outputDir`). |
82
+ | `retries` | `3` | Tentativas; a cada falha a extração é refeita com URL nova. |
83
+ | `client` | — | Força um cliente (`WEB`, `MWEB`, `ANDROID`, `IOS`, `TVHTML5`...). Sem isso, tenta em cadeia. |
84
+ | `proxy` | — | URL do proxy para esta chamada. Veja [Proxy](#proxy). |
85
+ | `stallTimeoutMs` | `30000` | Aborta o stream se ficar esse tempo sem receber dados. `0` desativa. |
86
+ | `cookieFile` | — | Arquivo de cookies Netscape. |
87
+ | `poToken` | `YOUTUBE_GVS_PO_TOKEN` | GVS PO token. |
88
+ | `headers` | — | Cabeçalhos extras. |
89
+ | `ffmpegPath` | `'ffmpeg'` | Caminho do executável. |
64
90
 
65
- ```bash
66
- npm run download:test -- "https://www.youtube.com/watch?v=VIDEO_ID" muxed 720
67
- ```
91
+ Quando o cliente só fornece um stream muxado (como o itag 18), `kind: 'audio'` baixa esse arquivo e o FFmpeg extrai o áudio, em vez de falhar por falta de áudio adaptativo.
68
92
 
69
- O terceiro argumento é opcional. Tipos disponíveis:
93
+ ### `YouTube.downloadAudioFromVideo(input, options)`
70
94
 
71
- - `muxed`: vídeo com áudio no mesmo stream, quando disponível;
72
- - `audio`: melhor stream de áudio disponível;
73
- - `video`: melhor stream de vídeo disponível, que pode não conter áudio.
95
+ Baixa o vídeo com áudio, extrai só o áudio com FFmpeg e apaga o temporário. Aceita as mesmas opções de rede de `downloadMedia`.
74
96
 
75
- Para áudio, o formato padrão é `m4a`, baixado diretamente do stream AAC original. Para gerar MP3, o módulo baixa o stream de áudio e faz a conversão local com FFmpeg:
97
+ ### `YouTube.extract(input, options)`
76
98
 
77
- ```bash
78
- node download-test.js "https://www.youtube.com/watch?v=VIDEO_ID" audio 0 mp3
99
+ Devolve `{ id, title, author, channelId, duration, viewCount, thumbnail, formats, expiresInSeconds, client, poTokenApplied }`. Cada item de `formats` tem `itag`, `mimeType`, `quality`, `audioQuality`, `bitrate`, `width`, `height`, `fps`, `contentLength`, `url`, `hasUrl` e `sourceClient`.
100
+
101
+ Opções: `client`, `webOnly`, `includePlayerJson` (anexa o JSON bruto do Player API em `playerJson`), `proxy`, `cookieFile`, `auth`, `poToken`, `headers` e `pageRetries`.
102
+
103
+ ### Seletores de formato
104
+
105
+ `findAudio`, `findVideo`, `findMuxed`, `sortAudio`, `sortVideo` e `sortMuxed` ignoram formatos sem URL. `findAudio(formats, { mimeType: 'audio/mp4' })` prioriza AAC (itag 140), mais compatível com conversão para MP3.
106
+
107
+ ### Outras funções
108
+
109
+ `extractVideoId`, `normalizeUrl`, `download`, `downloadWithRetry`, `convertToMp3`, `mergeVideoAudio`, `extractAudioFromVideo`, `loadYouTubeAuth`, `setDefaultProxy` e `bridgeDownload` também estão em `YouTube` e como exportações nomeadas.
110
+
111
+ ## Clientes do YouTube
112
+
113
+ Por padrão o módulo tenta `WEB`, `MWEB`, `ANDROID`, `IOS`, `TVHTML5` e variantes embutidas, nessa ordem, e usa o primeiro que devolver ao menos um formato com URL (ou `signatureCipher`). Respostas sem formatos utilizáveis, como as que trazem só `serverAbrStreamingUrl` (SABR), são descartadas e o próximo cliente é tentado.
114
+
115
+ ```js
116
+ // força um cliente; não há fallback para os outros
117
+ await YouTube.downloadMedia(url, { kind: 'audio', client: 'ANDROID' });
118
+
119
+ // só WEB, com o JSON bruto do Player API
120
+ const data = await YouTube.extract(url, { webOnly: true, includePlayerJson: true });
79
121
  ```
80
122
 
81
- Ou pela API:
123
+ Variáveis de ambiente equivalentes: `YOUTUBE_PLAYER_CLIENT=ANDROID` e `YOUTUBE_WEB_ONLY=1`.
124
+
125
+ Cada cliente recebe respostas diferentes. Os clientes web costumam exigir PO token para os streams; o `ANDROID` costuma entregar URLs diretas, às vezes só no muxed itag 18. Para obter MP3 a partir de qualquer um deles basta ter FFmpeg.
126
+
127
+ ## Uso em servidor ou hospedagem
128
+
129
+ O YouTube trata IPs de datacenter com mais desconfiança do que IPs residenciais. Em hospedagens, VPS e nuvem é comum receber "Faça login para confirmar que não é um bot", `UNPLAYABLE` sem `streamingData` ou `403` no `googlevideo.com`, mesmo com o mesmo código funcionando no seu computador ou celular. Um PO token ou cookies, sozinhos, não resolvem bloqueio por IP.
130
+
131
+ As saídas, da mais automática para a menos:
132
+
133
+ 1. **Proxy residencial** com sessão fixa. Veja [Proxy](#proxy).
134
+ 2. **Bridge**: outra máquina com IP residencial faz o download e o servidor só recebe o arquivo. Veja [Bridge](#bridge).
135
+ 3. Rodar o código em uma máquina com IP residencial.
136
+
137
+ ## Proxy
138
+
139
+ Todas as requisições (página, Player API, JavaScript do player e download do stream) podem sair por um proxy HTTP/HTTPS. Requer o pacote `undici`, instalado como dependência opcional.
82
140
 
83
141
  ```js
142
+ // 1. por chamada
84
143
  await YouTube.downloadMedia(url, {
85
144
  kind: 'audio',
86
- audioFormat: 'mp3',
87
- mp3Quality: '2'
145
+ proxy: 'http://usuario:senha@host:porta'
88
146
  });
147
+
148
+ // 2. padrão global do processo
149
+ YouTube.setDefaultProxy('http://usuario:senha@host:porta');
150
+
151
+ // 3. variável de ambiente
152
+ // YOUTUBE_PROXY=http://usuario:senha@host:porta
89
153
  ```
90
154
 
91
- O formato `m4a` não exige FFmpeg. O formato `mp3` exige que o executável `ffmpeg` esteja instalado e disponível no PATH; também é possível informar `ffmpegPath` na API.
155
+ Prioridade: opção `proxy` da chamada, depois `setDefaultProxy`, depois `YOUTUBE_PROXY`. Sem nenhum deles, o módulo usa o `fetch` normal e não precisa do `undici`.
156
+
157
+ **Sessão fixa é obrigatória.** A URL do stream fica presa ao IP que fez a consulta. Se o proxy trocar de IP entre a consulta e o download, o resultado é `403`. Use a opção de sessão fixa (sticky) do seu provedor, em geral um identificador no usuário ou uma porta dedicada, conforme a documentação dele. Se o IP girar no meio de uma tentativa, `retries` refaz a extração com URL nova.
158
+
159
+ Caracteres especiais na senha precisam estar codificados na URL (`encodeURIComponent`). O consumo de dados é o do vídeo: um MP4 de 360p de poucos minutos fica em torno de 10 MB; áudio gasta menos.
92
160
 
93
- Quando o cliente do YouTube fornecer somente um formato muxado, como o itag 18, o modo `audio` agora baixa esse arquivo e extrai o áudio com FFmpeg em vez de falhar por ausência de áudio adaptativo. O mesmo fallback é usado por `video-audio`. Na linha de comando, a altura pode ser informada como `720` ou `720p`.
161
+ ## Bridge
94
162
 
95
- ## Vídeo em qualidade maior
163
+ O bridge divide o trabalho em duas pontas:
96
164
 
97
- Streams muxados (`muxed`) costumam ficar limitados a 360p. Para obter uma resolução maior, use `kind: 'video'`: o módulo baixa o stream de vídeo adaptativo e o melhor áudio separadamente e combina os dois localmente com FFmpeg.
165
+ - **Servidor** (`yt-zmp-bridge`), em uma máquina com IP que o YouTube aceita, como um PC ou celular com Termux. Faz a consulta e o download.
166
+ - **Cliente** (`bridgeDownload`), na hospedagem. Pede o arquivo ao servidor e salva localmente.
167
+
168
+ ### Servidor
98
169
 
99
170
  ```bash
100
- node download-test.js "https://www.youtube.com/watch?v=VIDEO_ID" video 1080
171
+ BRIDGE_TOKEN='uma-senha-longa-e-aleatoria' npx yt-zmp-bridge
101
172
  ```
102
173
 
103
- Pela API:
174
+ | Variável | Padrão | Descrição |
175
+ | --- | --- | --- |
176
+ | `BRIDGE_TOKEN` | obrigatória | Mínimo de 12 caracteres. Enviada no cabeçalho `x-token`. |
177
+ | `PORT` | `3000` | Porta. |
178
+ | `BRIDGE_HOST` | `127.0.0.1` | Endereço de escuta. |
179
+ | `BRIDGE_MAX_PARALLEL` | `2` | Downloads simultâneos; acima disso responde `429`. |
180
+ | `YOUTUBE_PLAYER_CLIENT` | `ANDROID` | Cliente usado no servidor. |
181
+ | `BRIDGE_COOKIE_FILE` | — | Cookies Netscape opcionais. |
182
+
183
+ Para áudio, o servidor tenta o stream de áudio puro e, se falhar, cai para o muxed itag 18. Quem recebe converte para MP3 com FFmpeg.
184
+
185
+ Para ser alcançado pela hospedagem sem abrir portas, use um túnel de saída, como `cloudflared tunnel --url http://localhost:3000`. Em túneis temporários a URL muda a cada reinício, e túneis gratuitos podem cortar respostas que demoram mais de cerca de 100 s sem enviar dados.
186
+
187
+ ### Cliente
104
188
 
105
189
  ```js
106
- await YouTube.downloadMedia(url, {
107
- kind: 'video',
108
- height: 1080,
109
- mergeAudio: true
190
+ import { bridgeDownload } from 'yt-zmp/bridge';
191
+
192
+ const r = await bridgeDownload('https://youtu.be/VIDEO_ID', './downloads/musica', {
193
+ bridgeUrl: 'https://seu-tunel.exemplo.com', // ou BRIDGE_URL
194
+ token: process.env.BRIDGE_TOKEN, // ou BRIDGE_TOKEN
195
+ kind: 'audio', // 'audio' | 'video' | 'muxed'
196
+ audioFormat: 'm4a'
110
197
  });
198
+ // r.path => ./downloads/musica.m4a (a extensão vem do servidor)
111
199
  ```
112
200
 
113
- Se a resolução solicitada não existir, o módulo escolhe a melhor resolução disponível abaixo do limite informado. A combinação de vídeo de alta resolução exige FFmpeg; `mergeAudio: false` permite salvar apenas o stream de vídeo.
201
+ Os erros dizem o que aconteceu: token diferente (`401`), túnel fora do ar (`530`), túnel cortou por demora (`524`), servidor ocupado (`429`) ou sem resposta no tempo limite (`timeoutMs`, padrão 10 min).
202
+
203
+ **Segurança:** quem tiver a URL e o token consegue usar o servidor para baixar vídeos pela sua conexão. Use um token longo e aleatório e não o publique em repositório.
204
+
205
+ ## Cookies
114
206
 
115
- Os arquivos são salvos em `./downloads` por padrão. Também é possível usar a API:
207
+ Para vídeos que exigem conta, aceita um arquivo de cookies Netscape. Use apenas cookies da sua própria conta e mantenha o arquivo fora do repositório.
116
208
 
117
209
  ```js
118
- import YouTube from './index.js';
210
+ await YouTube.downloadMedia(url, { kind: 'audio', cookieFile: '/caminho/privado/cookies.txt' });
211
+ ```
119
212
 
120
- const result = await YouTube.downloadMedia(url, {
121
- kind: 'audio',
122
- outputDir: './downloads'
123
- });
213
+ Os cookies e o `SAPISIDHASH` são enviados só ao YouTube, nunca ao `googlevideo.com`. Cookies não garantem a superação de CAPTCHA ou de bloqueio por IP. Para conferir a sessão sem baixar nada:
214
+
215
+ ```bash
216
+ YOUTUBE_COOKIE_FILE=/caminho/privado/cookies.txt npm run auth:check -- "URL"
217
+ ```
218
+
219
+ O comando informa quantos cookies foram lidos e se o YouTube respondeu, sem imprimir valores.
220
+
221
+ ## GVS PO token
222
+
223
+ Algumas respostas exigem um GVS PO token nas URLs de mídia. O yt-zmp **não gera** esse token; se você tem um legítimo para a sessão e o cliente, informe-o e ele será anexado como `pot` às URLs:
224
+
225
+ ```js
226
+ await YouTube.downloadMedia(url, { kind: 'video', poToken: process.env.YOUTUBE_GVS_PO_TOKEN });
124
227
  ```
125
228
 
126
- ## Status experimental
229
+ Sem o token, alguns formatos aparecem na extração mas dão `403` no download. Não imprima nem versione o valor.
230
+
231
+ ## Erros comuns
232
+
233
+ | Sintoma | Causa provável |
234
+ | --- | --- |
235
+ | `sem formatos utilizáveis [UNPLAYABLE, sem streamingData]` | O YouTube recusou o cliente ou o IP. Tente outro `client`, um proxy residencial ou o bridge. |
236
+ | `403` no stream | URL presa a outro IP (proxy sem sessão fixa, extração e download em redes diferentes), PO token ausente ou URL expirada (`expiresInSeconds`). |
237
+ | `nenhuma URL pôde ser resolvida` | Falha ao decifrar a assinatura do player: o `cipher.js` precisa acompanhar uma mudança do YouTube. Atualize o pacote. |
238
+ | `Download travado: sem dados por 30s` | O stream parou de enviar. A tentativa é repetida; ajuste `stallTimeoutMs` se a rede for lenta. |
239
+ | `FFmpeg não encontrado` | Instale o FFmpeg ou informe `ffmpegPath`. |
240
+
241
+ Não reutilize nem salve URLs do `googlevideo.com`: são assinadas, expiram rápido e podem ficar presas ao IP da extração. O `downloadMedia` extrai uma URL nova a cada tentativa.
242
+
243
+ ## Variáveis de ambiente
244
+
245
+ | Variável | Uso |
246
+ | --- | --- |
247
+ | `YOUTUBE_PROXY` | Proxy padrão. |
248
+ | `YOUTUBE_PLAYER_CLIENT` | Cliente forçado. |
249
+ | `YOUTUBE_WEB_ONLY` | `1` para usar só o cliente WEB. |
250
+ | `YOUTUBE_GVS_PO_TOKEN` | GVS PO token. |
251
+ | `YOUTUBE_COOKIE_FILE` | Cookies, lidos pela linha de comando. |
252
+ | `BRIDGE_URL`, `BRIDGE_TOKEN` | Padrões de `bridgeDownload`. |
253
+
254
+ ## Desenvolvimento
255
+
256
+ ```bash
257
+ npm test
258
+ ```
127
259
 
128
- Este projeto ainda está em testes. As URLs de stream são temporárias, os formatos disponíveis variam por vídeo e o YouTube pode alterar seus endpoints ou exigir novos parâmetros. O módulo combina streams adaptativos de vídeo e áudio e converte áudio para MP3 localmente com FFmpeg. Use somente em conteúdos que você tem autorização para baixar e respeite os termos aplicáveis.
260
+ Os testes rodam offline e cobrem links, seletores de formato, filtro de respostas, proxy, detecção de travamento no download e o cliente do bridge. Os scripts em `scripts/` fazem testes manuais contra o YouTube e não vão no pacote publicado.
129
261
 
130
- ## Erro 403 em `googlevideo.com`
262
+ ## Licença
131
263
 
132
- Não reutilize nem salve a URL `googlevideo.com` retornada pelo YouTube. Ela é assinada, expira rapidamente e pode ficar vinculada ao IP que fez a extração. O fluxo `downloadMedia()` extrai uma URL nova a cada tentativa e baixa imediatamente, mas ainda pode falhar se a extração e o download forem executados em máquinas ou redes diferentes. Em caso de 403 persistente, verifique primeiro se o cliente exige GVS PO token, se a extração e o download ocorrem na mesma máquina/rede e se a URL ainda está dentro de `expiresInSeconds`. O downloader valida respostas parciais e tenta formatos alternativos, mas não consegue contornar uma política de autorização do servidor sem o token necessário. O projeto continua independente do yt-dlp; o código dele foi usado apenas como referência técnica.
264
+ MIT.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../src/bridge-server.js";
@@ -1,4 +1,5 @@
1
- import YouTube from './index.js';
1
+ #!/usr/bin/env node
2
+ import YouTube from '../index.js';
2
3
 
3
4
  const url = process.argv[2];
4
5
  const kind = process.argv[3] || 'muxed';
@@ -8,7 +9,7 @@ const height = process.argv[4]
8
9
  const audioFormat = process.argv[5] || 'm4a';
9
10
 
10
11
  if (!url) {
11
- console.log('Uso: node download-test.js "URL_DO_YOUTUBE" [audio|video|video-audio|muxed] [altura] [m4a|mp3]');
12
+ console.log('Uso: yt-zmp "URL_DO_YOUTUBE" [audio|video|video-audio|muxed] [altura] [m4a|mp3]');
12
13
  process.exit(1);
13
14
  }
14
15
 
@@ -28,13 +29,14 @@ if (height !== undefined && !Number.isFinite(height)) {
28
29
  }
29
30
 
30
31
  try {
31
- console.log('\n[EXPERIMENTAL] Consultando e baixando mídia do YouTube...\n');
32
+ console.log('\nConsultando e baixando mídia do YouTube...\n');
32
33
 
33
34
  const downloadOptions = {
34
35
  height,
35
36
  audioFormat,
36
37
  outputDir: './downloads',
37
38
  cookieFile: process.env.YOUTUBE_COOKIE_FILE,
39
+ proxy: process.env.YOUTUBE_PROXY,
38
40
  poToken: process.env.YOUTUBE_GVS_PO_TOKEN
39
41
  };
40
42
  const result = kind === 'video-audio'
package/index.js CHANGED
@@ -1,37 +1,16 @@
1
+ import { extractVideoId, normalizeUrl } from './src/parser.js';
2
+ import { extract } from './src/extractor.js';
3
+ import { download, downloadWithRetry } from './src/downloader.js';
4
+ import { downloadMedia, downloadAudioFromVideo } from './src/media.js';
5
+ import { loadYouTubeAuth } from './src/auth.js';
6
+ import { convertToMp3, mergeVideoAudio, extractAudioFromVideo } from './src/transcoder.js';
7
+ import { setDefaultProxy } from './src/proxy.js';
8
+ import { bridgeDownload } from './src/bridge-client.js';
1
9
  import {
2
- extractVideoId,
3
- normalizeUrl
4
- } from './parser.js';
5
-
6
- import {
7
- extract
8
- } from './extractor.js';
9
-
10
- import {
11
- download,
12
- downloadWithRetry
13
- } from './downloader.js';
14
-
15
- import {
16
- downloadMedia,
17
- downloadAudioFromVideo
18
- } from './media.js';
19
- import {
20
- loadYouTubeAuth
21
- } from './auth.js';
22
-
23
- import {
24
- createFormat,
25
- isAudio,
26
- isVideo,
27
- isMuxed,
28
- sortAudio,
29
- sortVideo,
30
- sortMuxed,
31
- findAudio,
32
- findVideo,
33
- findMuxed
34
- } from './formats.js';
10
+ createFormat, isAudio, isVideo, isMuxed,
11
+ sortAudio, sortVideo, sortMuxed,
12
+ findAudio, findVideo, findMuxed
13
+ } from './src/formats.js';
35
14
 
36
15
  export const YouTube = {
37
16
  extractVideoId,
@@ -44,23 +23,31 @@ export const YouTube = {
44
23
  loadYouTubeAuth,
45
24
 
46
25
  createFormat,
47
-
48
26
  isAudio,
49
27
  isVideo,
50
28
  isMuxed,
51
-
52
29
  sortAudio,
53
30
  sortVideo,
54
31
  sortMuxed,
55
-
56
32
  findAudio,
57
33
  findVideo,
58
34
  findMuxed,
59
35
 
60
36
  download,
61
- downloadWithRetry
37
+ downloadWithRetry,
38
+
39
+ convertToMp3,
40
+ mergeVideoAudio,
41
+ extractAudioFromVideo,
42
+
43
+ setDefaultProxy,
44
+ bridgeDownload
62
45
  };
63
46
 
64
- export { loadYouTubeAuth };
47
+ export {
48
+ loadYouTubeAuth,
49
+ setDefaultProxy,
50
+ bridgeDownload
51
+ };
65
52
 
66
53
  export default YouTube;
package/package.json CHANGED
@@ -1,36 +1,45 @@
1
1
  {
2
- "name": "yt-zmp",
3
- "version": "1.0.0",
4
- "type": "module",
5
- "description": "Biblioteca experimental para pesquisa, extração e download de mídias do YouTube, incluindo metadados, áudio e vídeo.",
6
- "main": "./index.js",
7
- "exports": {
8
- ".": "./index.js"
9
- },
10
- "files": [
11
- "*.js",
12
- "src/",
13
- "README.md",
14
- "LICENSE"
15
- ],
16
- "scripts": {
17
- "test": "node test.js",
18
- "download:test": "node download-test.js",
19
- "auth:test": "node cookie-test.js"
20
- },
21
- "engines": {
22
- "node": ">=18"
23
- },
24
- "keywords": [
25
- "yt-zmp",
26
- "youtube",
27
- "youtube-downloader",
28
- "youtube-search",
29
- "youtube-audio",
30
- "youtube-video",
31
- "media-downloader",
32
- "extractor"
33
- ],
34
- "license": "MIT",
35
- "dependencies": {}
36
- }
2
+ "name": "yt-zmp",
3
+ "version": "1.1.0",
4
+ "description": "Extração e download de áudio e vídeo do YouTube em Node.js, sem yt-dlp. Suporta proxy, cookies, PO token e um bridge para servidores com IP bloqueado.",
5
+ "type": "module",
6
+ "main": "./index.js",
7
+ "exports": {
8
+ ".": "./index.js",
9
+ "./bridge": "./src/bridge-client.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "bin": {
13
+ "yt-zmp": "./bin/yt-zmp.js",
14
+ "yt-zmp-bridge": "./bin/yt-zmp-bridge.js"
15
+ },
16
+ "files": [
17
+ "index.js",
18
+ "src/",
19
+ "bin/",
20
+ "README.md",
21
+ "LICENSE",
22
+ "CHANGELOG.md"
23
+ ],
24
+ "scripts": {
25
+ "test": "node --test",
26
+ "auth:check": "node scripts/cookie-check.js"
27
+ },
28
+ "engines": {
29
+ "node": ">=18.17"
30
+ },
31
+ "keywords": [
32
+ "youtube",
33
+ "youtube-downloader",
34
+ "youtube-audio",
35
+ "youtube-mp3",
36
+ "downloader",
37
+ "extractor",
38
+ "proxy",
39
+ "whatsapp-bot"
40
+ ],
41
+ "license": "MIT",
42
+ "optionalDependencies": {
43
+ "undici": "^6.21.0"
44
+ }
45
+ }