# MediaRip

A premium, dark-themed desktop app that rips audio from **YouTube and X/Twitter**
(and other sites yt-dlp supports) to **MP3** — or saves the full **video as MP4**
with its own dedicated download button. Spotify track, album, and playlist
links are supported as metadata sources: MediaRip reads the Spotify titles and
artists, searches for matching audio with yt-dlp, then tags each file with title,
artist, album, track number, year, and embedded cover art.

Built with [customtkinter](https://github.com/TomSchimansky/CustomTkinter) for a
modern, card-based interface — rounded cards, accent buttons, live preview, and
a collapsible activity drawer.

![MediaRip screenshot](assets/app_screenshot.png)

## Features

- **Multi-source** — YouTube, X/Twitter, **SoundCloud** (tracks/sets/playlists),
  **Bandcamp**, **Mixcloud**, **Vimeo**, and **Twitch** (and other sites yt-dlp
  supports). The preview shows the detected source.
- **Spotify link support** — paste Spotify track, album, or playlist URLs;
  MediaRip reads public Spotify metadata, searches for matching audio, and
  downloads through the same queue/history/tagging workflow. Spotify is metadata
  only: it does not download Spotify audio and does not require Spotify Premium.
- **Live preview** — paste a link and see the thumbnail, title, uploader,
  duration, and source before downloading. Multiple links show the first item
  plus a `+N additional URLs` badge.
- Paste multiple URLs (one per line) or drag & drop links or `.txt` lists
- **Smart search** — type a song name instead of a URL, e.g.
  `Kendrick Lamar - HUMBLE`, `J Cole No Role Modelz`, or `Drake Headlines`.
  A single search shows a result picker; multiple search lines auto-select the
  top result and use the normal queue.
- YouTube videos **and playlists**, plus `x.com` / `twitter.com` status videos
- **Playlists, sets & albums** — paste a YouTube playlist, SoundCloud set,
  Bandcamp album, or Spotify album/playlist; MediaRip detects it, asks before
  grabbing all tracks, and downloads them with per-track progress
- **Download queue** — a live per-track list (`↓` downloading %, `✓` done,
  `✗` failed) that skips tracks already in your history
- **Download history** (SQLite `~/.mediarip_downloads.db`) powers the Recent
  Downloads panel
- **Analytics dashboard** — open Settings → Analytics to see totals, unique
  tracks/artists/albums, source counts, storage estimates, top artists/albums,
  most-downloaded track, and recent activity from your local history database
- **Duplicate detection** — if a link was downloaded before, MediaRip asks
  *Download again / Skip* (with "apply to all" for batches)
- **Smart filename cleanup** (optional) — strips `(Official Video)`,
  `(Official Audio)`, `Lyrics`, `Visualizer`, `HD`, `4K`, etc.
- **Organize by artist** (optional) — files go into per-artist subfolders
- Default destination is **`%USERPROFILE%\Music\YT Downloader`** (created
  automatically on first launch)
- "Choose" folder button; the chosen folder is remembered between runs (restored
  only if it still exists, otherwise it falls back to the default)
- Optional auto-created timestamped batch subfolder (`playlist_YYYYMMDD_HHMMSS`)
- **Two explicit download buttons** — **Download MP3** rips audio, **Download
  MP4** saves the video (Best or 720p via Settings → Video quality); audio and
  video of the same link are tracked separately in history, so one never
  blocks the other
- **Bitrate selector** (128 / 192 / 256 / 320 kbps) and **embed-artwork** toggle
- Deterministic, collision-safe filenames: `Title [videoid].mp3`
- **Recent Downloads** card with thumbnails and a one-click "Open" folder button
- Single-line status, smooth progress bar with percentage + current file
- Collapsible **Activity** drawer (hidden by default) logging ✔ / ❌ / ⚠ events
- Threaded downloads — the UI never freezes; one bad URL is skipped, the rest
  keep going

## Requirements

- Python 3.9+ (tested on 3.13)
- [FFmpeg](https://ffmpeg.org/) on `PATH` (required for MP3 conversion)
- [aria2c](https://aria2.github.io/) on `PATH` (optional — used automatically if
  found; falls back to the built-in downloader if it fails)

## Installation

For source installs:

1. Install Python 3.13.
2. Install FFmpeg and confirm `ffmpeg -version` works in a new terminal.
3. Install dependencies with the command below.
4. Run MediaRip from source.

Install Python dependencies:

```powershell
py -m pip install -r requirements.txt
```

## Run from source

```powershell
py yt_mp3_downloader_gui.py
```

## Build the .exe (PyInstaller)

Release builds should follow [BUILD.md](BUILD.md). The short version is:

```powershell
py -m pip install pyinstaller
py -m PyInstaller --clean yt_mp3_downloader_gui.spec
```

The spec bundles the native `tkinterdnd2` (tkdnd) library, the `customtkinter`
theme files, the MediaRip logo, and the app icon, so the frozen build runs
standalone. The output is `dist\MediaRip.exe`.

> FFmpeg and aria2c are **not** bundled into the exe — they must be installed on
> the target machine and available on `PATH`.

## Supported Sources

- Direct downloads through yt-dlp: YouTube, X/Twitter, SoundCloud, Bandcamp,
  Mixcloud, Vimeo, Twitch, and other yt-dlp-supported sites.
- Playlists/collections: YouTube playlists/channels, SoundCloud sets, Bandcamp
  albums, Spotify albums/playlists.
- Spotify: metadata-only; MediaRip searches for matching audio through yt-dlp.
- Smart search: typed song names are resolved through `ytsearch5`.

## Updating yt-dlp

YouTube and X change their sites often, which breaks older versions of yt-dlp.
If downloads suddenly start failing, update it first — this fixes the large
majority of issues:

In the app, click **Update yt-dlp** in Settings. MediaRip runs the update in the
background and shows a success or failure status when it finishes.

From a terminal, the same update is:

```powershell
py -m pip install -U yt-dlp
```

When running the bundled `.exe`, rebuild it after updating so the new yt-dlp is
included:

```powershell
py -m pip install -U yt-dlp
py -m PyInstaller --clean yt_mp3_downloader_gui.spec
```

## Troubleshooting

**YouTube says "video unavailable" / "not available on this app"**
Almost always an out-of-date yt-dlp. Run `py -m pip install -U yt-dlp` (see
above). Also confirm the video isn't private, region-locked, or age-restricted.

**FFmpeg not found**
MP3 conversion requires FFmpeg on your `PATH`. Install it and verify with
`ffmpeg -version`. On Windows, install via `winget install Gyan.FFmpeg` (or
unzip a build and add its `bin` folder to `PATH`), then restart the app.

**aria2c fails ("unreachable network" or exits with an error)**
The app passes `--disable-ipv6=true` to avoid the common IPv6 failure, and if
aria2c still fails it automatically retries with yt-dlp's built-in downloader,
so downloads should complete either way. aria2c is optional — if it keeps
failing you can simply remove `aria2c` from your `PATH` to skip it entirely.

**X/Twitter download fails**
Both `x.com` and `twitter.com` status links are supported; the app normalizes
them automatically. Failures are usually because the post has no video, is from
a protected/private account, or is age-restricted. Make sure you're using a link
to a specific post (`.../status/<id>`), and update yt-dlp if it persists.

## Appearance

Dark-only theme:

| Role            | Color     |
| --------------- | --------- |
| Background      | `#121212` |
| Cards           | `#1E1E1E` |
| Accent          | `#FF3B30` |
| Accent (hover)  | `#FF5E57` |
| Text            | `#FFFFFF` |
| Secondary text  | `#A0A0A0` |
| Success         | `#34C759` |
| Warning         | `#FF9500` |
| Error           | `#FF453A` |

## Config

Settings persist at:

```
%USERPROFILE%\.yt_mp3_downloader_config.json
```

Stored keys: `last_folder`, `auto_subfolders`, `embed_artwork`, `bitrate`,
`clean_filenames`, `organize_by_artist`, `dupe_mode` (`ask`/`skip`/`always`).

Download history is a separate SQLite database:

```
%USERPROFILE%\.mediarip_downloads.db
```

## Tests

Run all suites with the Windows runner:

```powershell
run_tests.bat
```

Or individually:

```powershell
py test_url_paste.py   # URL box: right-click menu, paste, Ctrl+A, batch
py test_m1_m2.py       # source detection, filename cleanup, history, duplicates
py test_m3.py          # playlist detection, dedupe decision, download queue
py test_m4.py          # metadata extraction + ID3 tag writing
py test_m6.py          # Spotify URL detection, metadata mapping, search queries
py test_m7.py          # smart search detection, mapping, bulk search planning
py test_m8.py          # yt-dlp updater command and UI-state handling
py test_m9.py          # analytics aggregation and source counting
```

> Use **`py`** (the Python launcher), not bare `python`. On many setups
> `python` resolves to a different virtual environment that doesn't have
> MediaRip's dependencies (e.g. `customtkinter`), which makes the tests fail
> with `ModuleNotFoundError`. `py` selects the system Python 3.13 install that
> runs the app.
