Metadata-Version: 2.4
Name: tidal-dl-ultra
Version: 0.1.1
Summary: Downloader de terminal para o Tidal (Lossless/Hi-Res), irmão do qobuz-dl-ultra. Funciona no a-Shell (iOS/iPadOS).
License: GPL-3.0-or-later
Keywords: tidal,music,downloader,flac,hi-res,lossless,a-shell
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx
Requires-Dist: mutagen
Requires-Dist: colorama>=0.4.6
Requires-Dist: prompt_toolkit
Requires-Dist: tqdm
Provides-Extra: speed
Requires-Dist: rapidfuzz; extra == "speed"
Requires-Dist: brotli; extra == "speed"
Provides-Extra: keyring
Requires-Dist: keyring; extra == "keyring"
Provides-Extra: video
Requires-Dist: cryptography; extra == "video"
Provides-Extra: all
Requires-Dist: tidal-dl-ultra[keyring,speed,video]; extra == "all"
Requires-Dist: platformdirs; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-cov>=5; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Dynamic: license-file

# 𝗧𝗜𝗗𝗔𝗟-𝗗𝗟 𝗨𝗟𝗧𝗥𝗔

__Baixe músicas Lossless e Hi-Res do [Tidal](https://tidal.com/) direto do terminal — inclusive no iPhone/iPad com o [a-Shell](https://holzschu.github.io/a-Shell_iOS/).__

Irmão do **qobuz-dl-ultra**: mesma organização de código, mesma pasta/nome de arquivo, mesmos marcadores `[IN PROGRESS]`/`[INCOMPLETE]`, mesmo catálogo local (`library.db`), scan, sync de favoritos e `doctor`. Uma biblioteca que mistura os dois serviços funciona com os dois programas.

## 📑 Índice

* [✨ Funcionalidades](#-funcionalidades)
* [📥 Instalação](#-instalação)
  * [📱 iPhone/iPad (a-Shell)](#-iphoneipad-a-shell)
  * [💻 Desktop / servidor](#-desktop--servidor)
* [↺ Resetar e limpar](#-resetar-e-limpar)
* [🔑 Login](#-login)
* [💻 Uso](#-uso)
* [🗂️ Catálogo local, scan e sync](#️-catálogo-local-scan-e-sync)
* [⚙️ Configuração](#️-configuração)
* [🔧 Solução de problemas](#-solução-de-problemas)
* [🧪 Desenvolvimento](#-desenvolvimento)
* [⚠️ Aviso legal](#️-aviso-legal)

## ✨ Funcionalidades

* **Lossless e Hi-Res** (FLAC até 24-bit/192 kHz conforme o plano) e AAC. O programa respeita o máximo publicado pelo álbum e mostra a qualidade real de cada faixa (por exemplo, `24bit/48kHz`). Só desce um degrau quando a API confirma que o tier está indisponível (`--no-fallback` desliga); falha de rede, autenticação, rate limit ou manifesto inválido não vira fallback silencioso.
* **Python puro, sem ffmpeg obrigatório.** O Tidal entrega Hi-Res como DASH (FLAC dentro de MP4 fragmentado); o remux para FLAC nativo é feito em Python (`fmp4.py`), sem re-encode. ffmpeg é só plano B.
* **Funciona no a-Shell**: dependências obrigatórias sem extensão nativa (`httpx`, `mutagen`, `colorama`, `prompt_toolkit` e `tqdm`), sem keyring por padrão (token em arquivo `0600`), sem `aiosqlite`/`tenacity`; saída adaptada a tela estreita. `rapidfuzz`, `brotli` e `cryptography` ficam opcionais porque podem exigir wheels específicos no iOS.
* **Tags completas**: título/artista/álbum, faixa/disco, data, ISRC, `BARCODE` (UPC), copyright, BPM, **ReplayGain de faixa e álbum**, capa, letra e IDs do Tidal (`TIDALTRACKID`, `TIDALALBUMID`) — os IDs permitem ao `scan` reconhecer seus álbuns com certeza. FLAC e M4A.
* **Vídeos identificados**: MP4 recebe título, artista, álbum, data, capa e `TIDALVIDEOID`; quando o formato inevitável é MPEG-TS, as mesmas informações ficam em um sidecar JSON ao lado do vídeo.
* **Letras**: embutidas nas tags e, quando sincronizadas, também em `.lrc`.
* **Retomada inteligente**: pasta `[IN PROGRESS]` durante o download; se algo falha vira `[INCOMPLETE]` e a próxima execução só baixa o que faltou. Arquivos temporários usam `~tmp_` (sem ponto, para o app Arquivos do iOS).
* **Álbuns, faixas, playlists (com `.m3u` e `.m3u8`), artistas e vídeos musicais**, multi-disco em `CD 01`, `CD 02`. Vídeos associados a álbuns ficam em `video_directory/Albums/...`, fora da árvore de músicas.
* **Vídeo (HLS)**: escolhe a variante pela qualidade (`--video-quality low/medium/high`), decripta segmentos AES-128 quando o CDN usa (mecanismo padrão do próprio HLS, não é DRM), remux para `.mp4` via ffmpeg quando disponível — sem ffmpeg, fica um `.ts` (toca normalmente no VLC e na maioria dos players).
* **Letras com reforço**: usa a letra do Tidal quando existe; se não existir, tenta **Musixmatch** e depois [LRCLIB](https://lrclib.net) — sempre de forma assíncrona, sem travar outros downloads. Desative com `--no-lyrics-fallback`.
* **Letras retroativas**: `tidal-dl lyrics [DIR]` preenche letras em FLAC/M4A/MP3 que já estão na biblioteca, sem baixar o áudio novamente. Use `--dry-run` para revisar ou `--force` para substituir letras existentes.
* **Diagnóstico por item**: o resumo mostra índice de faixa/vídeo, qualidade alvo, qualidade entregue e o motivo de qualquer limitação ou fallback.
* **Dedup** por banco (`tidal_dl.db`) + **sentinela** `.streamrip.json` em cada álbum completo (vídeos usam só o banco).
* **Somente streams de áudio sem criptografia.** Se o Tidal devolver um stream de áudio protegido, o programa tenta a qualidade abaixo; não há descriptografia de áudio no projeto. (Vídeo é diferente: a criptografia AES-128 do HLS é padrão do formato, resolvida com a própria chave que o manifesto entrega — não é DRM.)

## 📥 Instalação

### 📱 iPhone/iPad (a-Shell)

1. Instale o **a-Shell** na App Store.
2. Instale as dependências (todas Python puro):
   ```sh
   pip install httpx mutagen colorama
   ```
3. Instale o programa (PyPI, quando publicado) **ou** copie a pasta do projeto para dentro do a-Shell:
   ```sh
   pip install tidal-dl-ultra
   # ou, a partir da pasta do projeto:
   pip install .
   ```
4. Diga onde ficam config e downloads (a pasta `~/Documents` é visível no app Arquivos):
   ```sh
   export TIDAL_DL_IOS_HOME="$HOME/Documents"
   ```
   Se não definir, o a-Shell é detectado sozinho e `~/Documents` é usado.
5. Use `python3 -m tidal_dl ...` (se o comando `tidal-dl` não existir no PATH do a-Shell — se aparecer `login: command not found`, é isso):
   ```sh
   python3 -m tidal_dl            # tela inicial: sessão, comandos e primeiros passos
   python3 -m tidal_dl login
   python3 -m tidal_dl dl https://tidal.com/browse/album/123456
   ```
   Para digitar só `tidal-dl`, crie um atalho (e coloque a mesma linha no arquivo de inicialização do a-Shell para valer sempre):
   ```sh
   alias tidal-dl='python3 -m tidal_dl'
   ```

Dicas para o a-Shell:
* Deixe a tela ligada durante downloads longos (o iOS suspende apps em segundo plano).
* Em rede móvel ruim use `--concurrency 1`.
* `python -m tidal_dl doctor` mostra o que falta, sem mexer em nada.
* Sem ffmpeg tudo funciona: o remux FLAC é interno.

### 💻 Desktop / servidor

```sh
pip install tidal-dl-ultra          # ou: pip install ".[all]" na pasta do projeto
tidal-dl login
```

Docker (NAS): `docker build -t tidal-dl-ultra . && docker run -it -v ./config:/config -v ./downloads:/downloads tidal-dl-ultra login`.

## ↺ Resetar e limpar

```sh
tidal-dl -r            # roda o assistente de configuração (cria ou substitui o config.ini)
tidal-dl -p            # apaga o banco de downloads-já-feitos (tidal_dl.db) -- não mexe nos arquivos
```

Na primeira vez que você rodar qualquer comando sem ter um `config.ini`, o assistente roda sozinho.

## 🔑 Login

```sh
tidal-dl login            # PKCE: Lossless / Hi-Res (recomendado)
tidal-dl login --device   # código de dispositivo: só AAC 320 kbps
tidal-dl user             # conta, país, plano e qualidade máxima
tidal-dl logout           # apaga o token
```

No login PKCE o programa mostra uma URL **de login**. Abra no navegador (pode ser o Safari do próprio iPhone), **entre na conta e autorize**. No fim o Tidal redireciona para uma página que dá erro ou fica em branco (é normal): **copie a URL da barra de endereço nesse momento** e cole no terminal. Ela começa com `https://tidal.com/android/login/auth?code=...`. O token renova sozinho.

Erros comuns: colar de volta a URL de login que o programa mostrou (o programa avisa e pergunta de novo, até 3 vezes, sem precisar recomeçar); ou, se o app do Tidal estiver instalado, ele abrir sozinho no fim do login e "engolir" o redirecionamento — nesse caso abra o link de novo em outro navegador ou em aba anônima.

O token fica em `credentials.json` (permissão 0600) ao lado do `config.ini`, ou no keyring do sistema quando existe. **Nunca compartilhe esse arquivo.**

## 💻 Uso

```sh
tidal-dl dl https://tidal.com/browse/album/123456          # álbum
tidal-dl dl https://tidal.com/browse/track/123456 -q 2     # faixa em FLAC 16-bit
tidal-dl dl https://tidal.com/browse/playlist/UUID         # playlist (+ .m3u/.m3u8)
tidal-dl lyrics ~/Music                                      # completa letras locais
tidal-dl inspect ~/Music --json                              # confere qualidade e tags
tidal-dl dl https://tidal.com/browse/artist/123 --eps      # discografia (+ EPs/singles)
tidal-dl dl https://tidal.com/browse/video/123456 --video-quality high
tidal-dl dl lista.txt                                      # várias URLs, uma por linha

tidal-dl search daft punk                # busca de álbuns; escolha por número (1,3-5)
tidal-dl search -t track get lucky       # tipos: album | track | artist | playlist
tidal-dl lucky -t album -n 3 pink floyd  # baixa direto os 3 primeiros

tidal-dl stats                           # estatísticas do que você baixou
```

Qualidades (`-q`): `0` AAC 96 · `1` AAC 320 · `2` FLAC 16/44.1 · `3` Hi-Res legado · `4` FLAC até 24/192.

Opções úteis: `-d PASTA`, `-ff` / `-tf` (formatos), `--max-workers N` (1 = sequencial com barra; >1 = paralelo), `--delay SEG` (força sequencial), `--no-progress`, `--no-db`, `--no-lyrics`, `--no-lyrics-fallback`, `--no-cover`, `--no-sentinel`, `--remux auto|python|ffmpeg|none`, `--video-directory PASTA`, `--video-quality low|medium|high`.

### Modo sequencial x paralelo

* **Sequencial** (`--max-workers 1`, o padrão): uma faixa por vez, com barra de progresso em tempo real. Melhor para acompanhar o que está acontecendo e para conexões instáveis (cada faixa retoma de onde parou se cair).
* **Paralelo** (`--max-workers N`, N > 1): várias faixas ao mesmo tempo. Barras desenhadas por cima umas das outras ficam ilegíveis, então cada faixa mostra uma linha "Em Progresso" e depois "Concluído".
* `--delay` sempre força o modo sequencial ("Safety Delay"), mesmo com `--max-workers` alto.

Cada álbum/faixa/playlist termina com um resumo (📊) mostrando quantas faixas foram baixadas, puladas, tiveram fallback de qualidade ou falharam.

### Variáveis de formatação

Pasta (`folder_format`): `{release_type}` `{album_artist}` `{album_title}` `{year}` `{format}` `{bit_depth}` `{sampling_rate}` `{album_id}` `{quality}`
Faixa (`track_format`): `{track_number}` `{disc_number}` `{track_title}` `{track_title_base}` `{track_artist}` `{album_artist}` `{explicit}` `{track_id}`

Padrão da pasta: `{release_type}/{album_artist} - {album_title} ({year}) [{format} {bit_depth}]`.

## 🗂️ Catálogo local, scan e sync

O `library.db` (ao lado do `config.ini`) responde: *"o que eu tenho na conta vs. o que eu tenho no disco?"*.

```sh
tidal-dl sync-favorites                    # diff (novos/removidos) e atualiza o catálogo
tidal-dl sync-favorites --download-new     # baixa o que foi favoritado desde a última vez
tidal-dl sync-favorites --download-missing --limit 20 -y
tidal-dl sync-favorites --download-new --every 60    # modo contínuo (NAS)

tidal-dl scan "/musica"                    # casa pastas do disco com o catálogo (offline)
tidal-dl scan --dry-run --json rel.json

tidal-dl library                           # status
tidal-dl library missing                   # favoritos ainda não baixados
tidal-dl library history                   # últimas sincronizações
tidal-dl library reconcile [DIR] [--fix]   # sentinelas do disco ⇄ catálogo
tidal-dl library reset-stuck               # destrava álbuns presos após CTRL+C
tidal-dl library unmark <ID>

tidal-dl doctor [--json]                   # diagnóstico (somente leitura, sem segredos)
```

**Biblioteca grande:** rode `sync-favorites` (sem download), depois `scan`, e só então `--download-missing`.

O `scan` decide por: tag `TIDALALBUMID` → UPC → nome exato → fuzzy. Só marca sozinho quando o match é único e a contagem de faixas bate; dúvida vai para revisão manual. Pastas `[INCOMPLETE]` nunca viram "completas". Multi-disco conta como um álbum.

## ⚙️ Configuração

`tidal-dl config` abre um assistente; `tidal-dl config --show` mostra tudo; `--reset` apaga. O arquivo é o `config.ini` (seção `[tidal]`), em:

| Ambiente | Pasta |
|---|---|
| a-Shell | `$TIDAL_DL_IOS_HOME/tidal-dl/` (ou `~/Documents/tidal-dl/`) |
| Linux/macOS | `~/.config/tidal-dl/` |
| Windows | `%APPDATA%\tidal-dl\` |
| Qualquer | `$CONFIG_DIR/tidal-dl/` (tem prioridade) |

Chaves principais: `directory`, `video_directory`, `quality`, `video_quality`, `allow_quality_fallback`, `folder_format`, `track_format`, `embed_art`, `save_cover_file`, `lyrics`, `lyrics_fallback`, `save_lrc`, `max_workers`, `progress_bar`, `retries`, `remux`, `no_database`, `write_sentinel`, `disable_keyring`. Argumento na linha de comando > `config.ini` > padrão.

## 🔧 Solução de problemas

* **"Você não está logado"** → `tidal-dl login`.
* **Só baixa AAC** → você entrou com `--device`; refaça o login PKCE. Confira também `tidal-dl user` (plano e qualidade máxima).
* **"Apenas prévia (PREVIEW)"** → assinatura inativa ou sem direito ao conteúdo.
* **Álbum ficou `[INCOMPLETE]`** → rode o mesmo comando de novo; só o que falhou é baixado.
* **Erro de remux** → `--remux ffmpeg` (se houver ffmpeg) ou `--remux none` para guardar o `.mp4` bruto.
* **Faixas sem tags** → falta `mutagen` (`pip install mutagen`).
* **Qualquer dúvida de ambiente** → `tidal-dl doctor`.

## 🧪 Desenvolvimento

```sh
pip install -e ".[dev]"
pytest
```

Os testes usam um Tidal falso (`tests/unit/fakes.py`, `scenario.py`): nunca tocam a rede. A camada HTTP (`net.py`) é injetável, por isso a lógica é testável sem `httpx`.

## ⚠️ Aviso legal

Este projeto é independente e **não é afiliado ao Tidal**. Use apenas com a sua própria assinatura, para uso pessoal, e respeite os Termos de Serviço e as leis de direitos autorais do seu país. Você é responsável pelo uso que fizer. As credenciais de cliente OAuth embutidas são as usadas pela comunidade de ferramentas Tidal e podem ser substituídas por variáveis de ambiente (`TIDAL_DL_CLIENT_ID`, `TIDAL_DL_CLIENT_SECRET`, `TIDAL_DL_CLIENT_ID_PKCE`, `TIDAL_DL_CLIENT_SECRET_PKCE`).

O download de vídeo foi implementado e testado com manifestos HLS sintéticos (inclusive com segmentos criptografados), mas nunca contra o CDN real do Tidal -- avise se algo não bater. A criptografia AES-128 que ele eventualmente decripta é o mecanismo padrão do próprio formato HLS (a chave vem no manifesto entregue pela sua sessão autenticada), não um DRM como Widevine/FairPlay. O fallback de letras tenta Musixmatch e depois consulta o [LRCLIB](https://lrclib.net), um banco aberto mantido justamente para esse tipo de consulta; desative com `--no-lyrics-fallback` se preferir usar só as letras do próprio Tidal.

Licença: GPL-3.0 (ver `LICENSE`).
