Metadata-Version: 2.4
Name: tgmusicdl
Version: 0.1.0
Summary: Download and organize music from Telegram channels.
Author-email: mndfq <mndfq.041@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/mndfq/tgmusicdl
Project-URL: Issues, https://github.com/mndfq/tgmusicdl/issues
Project-URL: Source, https://github.com/mndfq/tgmusicdl
Keywords: telegram,music,downloader,cli,flac,mp3
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: Android
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Utilities
Classifier: Typing :: Typed
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: telethon>=1.36
Requires-Dist: aiosqlite>=0.20
Requires-Dist: mutagen>=1.47
Requires-Dist: typer>=0.12
Requires-Dist: tomli-w>=1.0
Requires-Dist: python-socks[asyncio]>=2.4
Requires-Dist: rich>=13.7
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Dynamic: license-file

# tgmusicdl

A command-line tool for downloading and organizing music from Telegram channels.

Give it a channel. It indexes the audio, downloads it with resumable workers,
reads the embedded tags, and files everything into a proper `Artist - Album/`
library. It survives crashes, network loss, and killing the terminal mid-download.

```
Music/
├── Artist A - Album A/
│   ├── 01 - Intro.flac
│   └── 02 - Track.flac
└── Artist B - Album B/
    ├── 01 - Track.mp3
    └── 02 - Track.mp3
```

## Features

- **Resumable downloads.** Every download goes to a `.part` file first; the
  final filename only appears after an atomic rename. Kill the process, kill
  the machine, lose power — the next run picks up where it left off.
- **Crash-safe state.** The download queue lives in SQLite, not memory.
  Completed files are never redownloaded. Orphaned files from a crash are
  adopted on restart.
- **Real metadata.** After download, Mutagen reads the embedded tags so the
  file lands in the right album folder with the right track number — not
  whatever Telegram's message said.
- **Album-aware organization.** Featured artists don't split albums.
  `A feat. B` on an album by `A` goes in `A`'s folder, not `A feat. B`'s.
  Different releases with the same album title stay separate.
- **Flat mode.** For channels that only post singles: `sync --flat` skips
  album grouping and drops files directly under the music directory.
- **MTProto and SOCKS5 proxies.** Both are supported.
- **Live progress.** A Rich-based HUD shows per-worker progress, transfer
  rates, ETAs, and phase status (recovery → index → download).
- **One channel at a time.** Switching channels asks first, then clears the
  previous channel's database rows. Files already on disk are left alone.

## Requirements

- Python 3.12 or newer
- A Telegram account
- API credentials from https://my.telegram.org

## Install

```bash
pip install tgmusicdl
```

Or from source:

```bash
git clone https://github.com/mndfq/tgmusicdl
cd tgmusicdl
pip install -e .
```

On Arch, you may prefer to install into a virtualenv rather than system
Python (which is PEP 668 externally-managed):

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e .
```

## Quick start

```bash
# 1. Configure your API credentials and music directory
tgmusicdl setup

# 2. Log in to Telegram (phone number + code)
tgmusicdl login

# 3. Find the channel you want
tgmusicdl channels

# 4. Sync it
tgmusicdl sync @somechannel
```

That's it. `sync` indexes the channel and downloads every track that isn't
already on disk. Run it again any time — it will pick up newly posted tracks
and resume anything that was interrupted.

If you don't know the channel's username, run `tgmusicdl sync` with no
argument and pick from your dialogs interactively.

### Singles channels

For channels that post individual tracks rather than albums:

```bash
tgmusicdl sync @singleschannel --flat
```

Files land directly under your music directory as `Artist - Title.ext`
instead of `Artist - Album/`.

### Resuming

If a sync was interrupted, or you just want to top up the current channel:

```bash
tgmusicdl resume
```

Equivalent to running `sync` on the same channel again, without having to
remember the reference.

## Configuration

The config lives at:

| Platform | Path |
|----------|------|
| Linux    | `~/.config/tgmusicdl/config.toml` |
| macOS    | `~/Library/Application Support/tgmusicdl/config.toml` |
| Windows  | `%APPDATA%\tgmusicdl\config.toml` |

Override the location with `TGMUSICDL_CONFIG=/path/to/config.toml`.

```toml
[telegram]
api_id = 123456
api_hash = "..."

[storage]
music_dir = "/home/you/Music"
db_path = ""                              # optional; default is next to the config
session_path = ""                         # optional; default is next to the config

[download]
workers = 3

[telegram.proxy]
type = "none"                             # "none" | "socks5" | "mtproto"
host = ""
port = 0
username = ""
password = ""
secret = ""
```

### Proxies

**SOCKS5** (uncomment and fill in):

```toml
[telegram.proxy]
type = "socks5"
host = "127.0.0.1"
port = 1080
username = ""            # optional
password = ""            # optional
```

**MTProto** (secret is the hex string your provider gives you):

```toml
[telegram.proxy]
type = "mtproto"
host = "127.0.0.1"
port = 443
secret = "ee367aa1d3f3d1e1..."
```

API hashes, proxy passwords, and MTProto secrets are redacted from all log
output.

## Commands

| Command | Purpose |
|---------|---------|
| `setup` | Configure API credentials, music directory, proxy |
| `login` | Authenticate with Telegram and save the session |
| `channels` | List channels your account can access |
| `sync [channel]` | Index and download a channel (or pick interactively) |
| `resume` | Continue the current channel |
| `status` | Show track counts for the current channel |

`sync` flags:

- `--flat` — write files directly under `music_dir` instead of album folders
- `--yes`, `-y` — skip the confirmation prompt when switching channels
- `--verbose`, `-v` — print full logs instead of the live HUD

## How it works

Everything that matters is in SQLite. The database is the single source of
truth: which channels are tracked, which tracks exist, which downloads are
queued, and where the index left off. Nothing important lives only in memory.

Each download follows a strict sequence:

```
DB: QUEUED
    ↓
write to <music_dir>/.tgmusicdl/parts/<track_id>.part
    ↓
fsync, validate size
    ↓
read embedded tags
    ↓
compute final path from the fresh metadata
    ↓
atomic os.replace(.part, final)
    ↓
DB: COMPLETE
```

If the process dies at any point, the next run reconciles. A `.part` file is
never treated as complete. A completed file is never overwritten by an
incomplete download. A crash between the rename and the database write is
detected on restart — the orphaned file is adopted, not duplicated.

## Development

```bash
git clone https://github.com/mndfq/tgmusicdl
cd tgmusicdl
python -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
pytest
```

The test suite covers the downloader, indexer, organizer, recovery pass,
metadata reader, and CLI. All Telegram I/O is stubbed; the database is real.

## Known limitations

- `sync --watch` (continuous polling) is not implemented. Run `sync` from
  cron or a systemd timer if you want it to run periodically.
- The library organizes *new* downloads. It does not move files that were
  already on disk before you started using tgmusicdl.
- The test suite does not run against live Telegram. That has to be done
  manually.

## License

MIT. See [LICENSE](LICENSE).
