Metadata-Version: 2.5
Name: fm-dlp-core
Version: 0.1.5.2
Summary: Core containing utilities for searching from Youtube/YTMusic and downloading audio/video from 1000+ sites
Project-URL: Homepage, https://github.com/Fkernel653/fm-dlp
Project-URL: Repository, https://github.com/Fkernel653/fm-dlp.git
Project-URL: Documentation, https://github.com/Fkernel653/fm-dlp#readme
Author: Fkernel653
License-Expression: AGPL-3.0-only
License-File: LICENSE
Keywords: agplv3,ffmpeg,framework,lib,libraries,library,youtube,youtube-music,yt-dlp,yt-dlp-wrapper,ytmusic
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
Classifier: Operating System :: OS Independent
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: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Multimedia :: Video
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Requires-Dist: mutagen
Requires-Dist: yt-dlp
Requires-Dist: ytmusicapi
Provides-Extra: dev
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

# fm-dlp-core — Core Library for YouTube & 1000+ Sites

[![Python](https://img.shields.io/badge/Python-3.10+-3776AB?logo=python&logoColor=fff&style=for-the-badge)](https://python.org)
[![PyPI](https://img.shields.io/pypi/v/fm-dlp-core?style=for-the-badge&logo=pypi&logoColor=fff&label=PyPI&color=007ec6)](https://pypi.org/project/fm-dlp-core)
[![License](https://img.shields.io/badge/License-GPLv3-00b96b?style=for-the-badge)](LICENSE)
[![Platform](https://img.shields.io/badge/Platform-Linux%20%7C%20macOS%20%7C%20Windows-9cf?style=for-the-badge)]()
[![Ruff](https://img.shields.io/badge/Code%20Style-Ruff-ff69b4?logo=ruff&logoColor=fff&style=for-the-badge)](https://docs.astral.sh/ruff)

**fm-dlp-core** is a powerful Python library for searching and downloading content from YouTube, YouTube Music, and over 1000+ supported sites. Built on top of yt-dlp, it provides a clean, async-first API with rich features including concurrent downloads, metadata embedding, and flexible output formatting.

---

## ✨ Key Features

| Feature | Description |
|---------|-------------|
| 🎵 **Audio Extraction** | Extract audio in 8 formats: MP3, AAC, FLAC, M4A, Opus, Vorbis, WAV, ALAC |
| 🎬 **Video Download** | Download videos in MP4, MKV, WebM, MOV, AVI, FLV with quality selection |
| 🔍 **Search** | Search YouTube videos and YouTube Music tracks/albums with formatted output |
| ⚡ **Concurrent Downloads** | Download multiple files in parallel with configurable job limits |
| 🏷️ **Metadata Embedding** | Automatically embed tags and thumbnails into audio files |
| 🔐 **Authentication** | Support for cookies (file or browser) to access restricted content |
| 💾 **Persistent Config** | Save and load download preferences across sessions |
| 🎨 **Colored Output** | Beautiful terminal output with ANSI colors (toggleable) |
| 🔌 **Extensible** | Create custom search providers for any platform |

---

## 📋 Table of Contents

- [Quick Start](#-quick-start)
- [Installation](#-installation)
- [Requirements](#-requirements)
- [Core Concepts](#-core-concepts)
- [Downloading Content](#-downloading-content)
- [Searching Content](#-searching-content)
- [Configuration](#-configuration)
- [Advanced Topics](#-advanced-topics)
- [API Reference](#-api-reference)
- [Examples](#-examples)
- [License](#-license)

---

## 🚀 Quick Start

```python
import asyncio
from fm_dlp_core import search, run_downloader

# 1. Search for a track
for result in search("Sewerslvt", limit=3, yt_video=False, album=False):
    print(result)

# 2. Download a track
asyncio.run(
    run_downloader(
        url="https://music.youtube.com/watch?v=y55fzyXZDSE",
        codec="mp3",
        kbps=320,
        path="./music",
        metadata=True,
        color=True,
    )
)
```

---

## 📦 Installation

```bash
pip install fm-dlp-core
```

For development:
```bash
git clone https://github.com/Fkernel653/fm-dlp-core
cd fm-dlp-core
pip install -e .
```

---

## ⚙️ Requirements

### Python Version
- **Python 3.10+** — Required for asyncio and type hint features

### FFmpeg (Required)
FFmpeg is essential for audio/video processing, conversion, and metadata embedding.

| Platform | Installation Command |
|----------|---------------------|
| **macOS** | `brew install ffmpeg` |
| **Debian/Ubuntu** | `sudo apt install ffmpeg` |
| **Fedora** | `sudo dnf install ffmpeg` |
| **Windows** | Download from [ffmpeg.org](https://ffmpeg.org/download.html) and add to PATH |

---

## 🧠 Core Concepts

### Async-First Design
All download operations are asynchronous, allowing you to run multiple downloads concurrently without blocking your application.

### Configuration Persistence
Settings like codec, bitrate, quality, and download path can be saved to a JSON file and reused across sessions.

### Provider Pattern
Search functionality is built on a provider pattern, making it easy to add support for new platforms by subclassing `BaseProvider`.

---

## 🎵 Downloading Content

### Basic Usage

```python
import asyncio
from fm_dlp_core import run_downloader

asyncio.run(
    run_downloader(
        url="https://youtube.com/watch?v=VIDEO_ID",
        codec="mp3",
        kbps=192,
        path="./downloads",
    )
)
```

### Advanced Usage with Context Manager

```python
from fm_dlp_core import Download

async def download_video():
    async with Download(
        url="https://youtube.com/watch?v=VIDEO_ID",
        codec="mp4",
        quality="1080p",
        jobs=4,
        path="./videos",
        metadata=True,
        only_video=True,
        color=True,
    ) as downloader:
        await downloader.download_all()
```

### Batch Downloads

```python
# Multiple URLs (comma or space separated)
async with Download(
    url="url1,url2,url3",  # or "url1 url2 url3"
    codec="flac",
    kbps=0,  # Lossless
    jobs=3,
) as downloader:
    await downloader.download_all()

# URLs from a text file (one per line)
async with Download(
    url="urls.txt",
    codec="m4a",
    kbps=256,
) as downloader:
    await downloader.download_all()
```

### Supported Codecs

| Type | Formats |
|------|---------|
| **Audio** | `mp3`, `aac`, `flac`, `m4a`, `opus`, `vorbis`, `wav`, `alac` |
| **Video** | `mp4`, `mov`, `mkv`, `webm`, `avi`, `flv` |

### Download Parameters

| Parameter | Type | Description | Default |
|-----------|------|-------------|---------|
| `url` | `str` | URL(s) to download (comma/space separated or path to file) | **Required** |
| `codec` | `str` | Output format (see Supported Codecs) | `"m4a"` (macOS) / `"opus"` (others) |
| `kbps` | `int` | Audio bitrate in kbps | `192` |
| `quality` | `str` | Video quality (`best`, `worst`, `1080p`, `720p`, etc.) | `"best"` |
| `jobs` | `int` | Number of concurrent downloads | `4` |
| `quiet` | `bool` | Suppress output messages | `False` |
| `metadata` | `bool` | Embed metadata and thumbnail | `True` |
| `keep` | `bool` | Keep original downloaded file | `False` |
| `save` | `bool` | Save parameters to config | `False` |
| `use_config` | `bool` | Load parameters from config | `False` |
| `path` | `str` | Download directory | Current directory |
| `only_video` | `bool` | Download video only (skip audio extraction) | `False` |
| `cookies` | `str \| None` | Cookies file path or browser name | `None` |
| `color` | `bool` | Enable colored output | `True` |

---

## 🔍 Searching Content

### YouTube Music Search

Search for tracks and albums on YouTube Music:

```python
from fm_dlp_core import search

# Search for tracks
for result in search(
    query="Sewerslvt",
    limit=5,
    yt_video=False,   # Use YouTube Music
    album=False,      # Search for tracks
    color=True,
):
    print(result)

# Search for albums
for result in search(
    query="Draining Love Story",
    limit=3,
    yt_video=False,
    album=True,       # Search for albums
):
    print(result)
```

### YouTube Video Search

Search for videos on YouTube:

```python
# Search YouTube videos
for result in search(
    query="Python tutorial",
    limit=5,
    yt_video=True,    # Use YouTube (videos)
    album=False,
):
    print(result)
```

### Output Modes

| Mode | Parameter | Description |
|------|-----------|-------------|
| **Formatted** | `raw=False, only_url=False` | Beautiful colored output with metadata |
| **URL-Only** | `only_url=True` | Just the URLs (great for piping) |
| **Raw Data** | `raw=True` | Python dictionaries with full metadata |

```python
# Get only URLs
urls = list(search("breakcore", limit=10, only_url=True))

# Get raw data
for data in search("Goreshit", limit=2, raw=True):
    print(data["title"], data["url"])

# Chain search → download
urls = list(search("chill beats", limit=5, only_url=True))
if urls:
    asyncio.run(run_downloader(url=" ".join(urls), codec="mp3", kbps=320))
```

### Search Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `query` | `str` | Search query string |
| `limit` | `int` | Maximum number of results (1-100) |
| `yt_video` | `bool` | `True` = YouTube videos, `False` = YouTube Music |
| `album` | `bool` | `True` = search albums, `False` = search tracks |
| `raw` | `bool` | Output raw Python dicts |
| `only_url` | `bool` | Output only URLs |
| `color` | `bool` | Enable colored output |

---

## ⚙️ Configuration

### Persistent Download Parameters

Save your preferred settings to reuse them later:

```python
from fm_dlp_core.utils.config.parametrs import set_parameters, get_parameters

# Save parameters
set_parameters(
    codec="mp3",
    kbps=256,
    quality="720p",
    jobs=4,
    quiet=False,
    metadata=True,
    keep=False,
    only_video=False,
    cookies="chrome",  # Use browser cookies
    color=True,
)

# Load saved parameters
params = get_parameters(color=True)
print(params)  # {'codec': 'mp3', 'kbps': 256, ...}
```

### Download Path

Set and get the default download directory:

```python
from fm_dlp_core.utils.config.path import set_path, get_path

# Set download path
result = set_path("/my/download/folder", color=True)
print(result)  # "Configuration saved successfully"

# Get current path
path = get_path(color=True)
print(f"Downloads will be saved to: {path}")
```

### Configuration File Location

| Platform | Path |
|----------|------|
| **Windows** | `%LOCALAPPDATA%\fm-dlp\config.json` |
| **macOS** | `~/Library/Application Support/fm-dlp/config.json` |
| **Linux** | `~/.config/fm-dlp/config.json` |

### Configuration Structure

```json
{
    "parameters": {
        "codec": "mp3",
        "kbps": 256,
        "quality": "best",
        "jobs": 4,
        "quiet": false,
        "metadata": true,
        "keep": false,
        "only_video": false,
        "cookies": null
    },
    "path": "/home/user/Music"
}
```

---

## 🔧 Advanced Topics

### Custom Search Providers

Create your own search provider by subclassing `BaseProvider`:

```python
from fm_dlp_core.commands.search.providers import BaseProvider

class SoundCloudProvider(BaseProvider):
    def _extract_results(self, query: str, limit: int, is_track: bool) -> list:
        # Implement your search logic
        # Return list of entries (dicts)
        return results

    def _extract_url(self, entry: dict, is_track: bool) -> str | None:
        return entry.get("permalink_url")

    def _fmt_entry(self, entry: dict, num: int, is_track: bool) -> str | None:
        return self.formatter.fmt_result(
            num=num,
            title=entry.get("title", "Unknown"),
            artist=entry.get("user", {}).get("username", "Unknown"),
            url=self._extract_url(entry, is_track),
            is_yt_video=False,
            is_track=is_track,
        )

    def _get_empty_message(self, query: str, is_track: bool) -> str:
        return f"No results found for '{query}'\n"

# Use your provider
provider = SoundCloudProvider(color=True, error_prefix="Error: ")
for result in provider.search(query="lo-fi", limit=5, is_track=True):
    print(result)
```

### Building Custom yt-dlp Options

For advanced use cases, you can build custom yt-dlp options:

```python
from fm_dlp_core.commands.downloader.options_builder import OptionsBuilder

builder = OptionsBuilder(
    codec="mp3",
    kbps=320,
    quality="best",
    jobs=4,
    quiet=False,
    metadata=True,
    keep=False,
    only_video=False,
    cookies="firefox",
    path="./downloads",
    color=True,
)

opts = builder.build()
# Add custom options
opts["extractor_args"] = {"youtube": {"skip": ["hls"]}}

# Use with yt-dlp directly
from yt_dlp import YoutubeDL
with YoutubeDL(opts) as ydl:
    ydl.download(["https://youtube.com/watch?v=..."])
```

### Cookie Authentication

For private or age-restricted content:

```python
# Using browser cookies
asyncio.run(
    run_downloader(
        url="https://youtube.com/watch?v=...",
        codec="mp3",
        cookies="chrome",  # or "firefox", "edge", "opera"
        path="./downloads",
    )
)

# Using cookies file
asyncio.run(
    run_downloader(
        url="https://youtube.com/watch?v=...",
        codec="mp3",
        cookies="./cookies.txt",
        path="./downloads",
    )
)
```

---

## 📚 API Reference

### Core Package

| Module | Description |
|--------|-------------|
| `fm_dlp_core` | Main package with `Download`, `Search`, and utilities |
| `fm_dlp_core.commands.downloader` | Download functionality with `Download` and `run_downloader` |
| `fm_dlp_core.commands.search` | Search functionality with `Search` and `search` |
| `fm_dlp_core.utils` | Shared utilities (colors, constants, config) |
| `fm_dlp_core.utils.config` | Configuration management (paths, parameters) |
| `fm_dlp_core.utils.colors` | Terminal color utilities |

### Key Classes

| Class | Module | Description |
|-------|--------|-------------|
| `Download` | `commands.downloader` | Async downloader with context manager |
| `DownloadConfig` | `commands.downloader.config` | Configuration container |
| `OptionsBuilder` | `commands.downloader.options_builder` | yt-dlp options builder |
| `URLParser` | `commands.downloader.url_parser` | Parse URLs from string/file |
| `Search` | `commands.search` | Main search handler |
| `ResultFormatter` | `commands.search.formatters` | Format search results |
| `BaseProvider` | `commands.search.providers` | Abstract provider base |
| `YouTubeProvider` | `commands.search.providers` | YouTube video search |
| `YouTubeMusicProvider` | `commands.search.providers` | YouTube Music search |

### Key Functions

| Function | Module | Description |
|----------|--------|-------------|
| `run_downloader` | `commands.downloader` | Async download entry point |
| `search` | `commands.search` | Convenience search function |
| `set_parameters` | `utils.config.parametrs` | Save download parameters |
| `get_parameters` | `utils.config.parametrs` | Load download parameters |
| `set_path` | `utils.config.path` | Set download directory |
| `get_path` | `utils.config.path` | Get download directory |
| `echo` | `utils` | Print with color support |
| `success/error/info/hint` | `utils.colors` | Formatted colored messages |

---

## 💡 Examples

### Example 1: Download a Music Playlist

```python
import asyncio
from fm_dlp_core import search, run_downloader

async def download_playlist(playlist_url: str):
    await run_downloader(
        url=playlist_url,
        codec="flac",
        kbps=0,  # Lossless
        jobs=4,
        metadata=True,
        path="./music",
        color=True,
    )

asyncio.run(download_playlist("https://music.youtube.com/playlist?list=..."))
```

### Example 2: Search and Download Top Tracks

```python
import asyncio
from fm_dlp_core import search, run_downloader

def get_top_tracks(artist: str, limit: int = 5) -> list[str]:
    return list(search(artist, limit=limit, yt_video=False, only_url=True))

async def download_artist(artist: str):
    urls = get_top_tracks(artist, limit=3)
    if urls:
        await run_downloader(
            url=" ".join(urls),
            codec="mp3",
            kbps=320,
            metadata=True,
            path=f"./music/{artist}",
        )

asyncio.run(download_artist("Porter Robinson"))
```

### Example 3: Custom Download with Progress Callback

```python
import asyncio
from fm_dlp_core import Download

class MyDownloader(Download):
    def _sync_download(self, url: str):
        # Override to add custom behavior
        print(f"Downloading: {url}")
        super()._sync_download(url)

async def main():
    async with MyDownloader(
        url="https://youtube.com/watch?v=...",
        codec="mp4",
        quality="1080p",
        path="./videos",
    ) as downloader:
        await downloader.download_all()
```

### Example 4: Working with Raw Search Data

```python
from fm_dlp_core import search

# Get raw data for programmatic use
for result in search(
    query="Daft Punk",
    limit=10,
    yt_video=False,
    album=False,
    raw=True,  # Returns dicts
):
    print(f"Title: {result['title']}")
    print(f"Artist: {result.get('artists', [{}])[0].get('name', 'Unknown')}")
    print(f"Duration: {result.get('duration')}s")
    print(f"URL: https://music.youtube.com/watch?v={result.get('videoId')}")
    print("-" * 40)
```

### Example 5: Error Handling

```python
import asyncio
from fm_dlp_core import run_downloader

async def safe_download(url: str):
    try:
        await run_downloader(
            url=url,
            codec="mp3",
            kbps=192,
            path="./downloads",
        )
    except Exception as e:
        print(f"Download failed for {url}: {e}")

asyncio.run(safe_download("https://youtube.com/watch?v=invalid_id"))
```

---

## 🖥️ Output Formatting

### Search Results Format

```
    1. Mr. Kill Myself
        ├─ Sewerslvt
        ├─ Draining Love Story
        ├─ 13,456,789 │ 7:52
        └─ https://music.youtube.com/watch?v=y55fzyXZDSE
           ──────────────────────────────────────────────────
```

### Format Elements

| Element | Description |
|---------|-------------|
| **N.** | Sequential result number |
| **Title** | Track, album, or video title |
| **Artist** | Artist or channel name |
| `├─└─│` | Tree structure for visual hierarchy |
| **Views │ Duration** | View count and length |
| **URL** | Direct link to content |

### Colored Output Functions

```python
from fm_dlp_core.utils.colors import success, error, info, hint

print(success("Download completed!"))
print(error("Failed to process video"))
print(info("Extracting metadata..."))
print(hint("Try using a higher bitrate for better quality"))
```

---

## 📄 License

This project is licensed under the **GPLv3 License** — see the [LICENSE](LICENSE) file for details.

### Acknowledgments

| Library | Purpose |
|---------|---------|
| [yt-dlp](https://github.com/yt-dlp/yt-dlp) | Download engine supporting 1000+ sites |
| [ytmusicapi](https://github.com/sigma67/ytmusicapi) | YouTube Music search API |
| [mutagen](https://github.com/quodlibet/mutagen) | Metadata tagging for audio files |

---

**Author:** [Fkernel653](https://github.com/Fkernel653)  
**Project:** [GitHub](https://github.com/Fkernel653/fm-dlp-core) • [PyPI](https://pypi.org/project/fm-dlp-core)  
**Documentation:** [fm-dlp-core Docs](https://github.com/Fkernel653/fm-dlp-core#readme)

---

*If you encounter any issues, please [open an issue](https://github.com/Fkernel653/fm-dlp-core/issues) on GitHub.*
