Metadata-Version: 2.4
Name: ap_ds
Version: 3.1.2
Summary: Audio Player By DVS - Advanced audio processing and playback
Home-page: https://www.dvsyun.top/ap_ds
Author: DVS
Author-email: me@dvsyun.top
License: License information can be found in the PyPi documentation or official website: dvsyun.top/ap_ds
Project-URL: Documentation, https://apds.top
Project-URL: License Info, https:/apds.top
Keywords: audio music player playback sdl2 dvs
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.7
Description-Content-Type: text/markdown
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: project-url
Dynamic: requires-python
Dynamic: summary

# ap_ds: A Lightweight Python Audio Library

**Current Version: v3.1.3  – Performance & Batch Parsing Edition (July 2026)**  
**Stable Release Recommendation: v3.0.0 LTS (Long-Term Support)**
## ⚠️ Version Notice !!!

**v3.1.0 and v3.1.1 are DEPRECATED and should NOT be used.**


### Why?

Both versions contain a **critical import bug** that causes `get_audio_metadata()` to return `None` in certain environments.

The issue was caused by a circular import chain:
```
player.py → audio_parser.py → audio_info.py → audio_parser.py (circular!)
```

The error was discovered by a developer using the pseudonym 'Star Sky', who reported the issue and provided a clean refactoring solution.



### What was the bug?

1. `audio_parser.py` imported `audio_info.py`
2. `audio_info.py` imported `audio_parser.py` (circular dependency)
3. Result: `get_audio_metadata()` returned `None` in some cases
4. The bug only appeared in certain import contexts, making it hard to detect


### How was it fixed?

**Star Sky's refactoring:**
- Removed the redundant `audio_parser.py` wrapper layer
- Merged `audio_info.py` into `audio_parser.py`
- Exposed top-level functions directly: `get_audio_duration()`, `get_audio_metadata()`
- `player.py` remains unchanged — fully compatible


### What to do?

**DO NOT install v3.1.0 or v3.1.1.**

```bash
# Upgrade to v3.1.2 immediately
pip install --upgrade ap_ds
```

If you already installed a deprecated version:
```bash
pip install --upgrade ap_ds==3.1.2
```


### Credits

Special thanks to **Star Sky** for:
- Discovering the bug
- Providing a complete refactoring
- Testing the fix

Your contribution made ap_ds more reliable for everyone! 🙏


### Version Status

| Version | Status | Action |
|---------|--------|--------|
| v3.0.0 LTS | ✅ Stable | Production-ready |
| v3.1.0 | ❌ DEPRECATED | Do not use |
| v3.1.1 | ❌ DEPRECATED | Do not use |
| **v3.1.2** | ✅ **Current Stable** | **Recommended** |

```bash
pip install ap_ds==3.1.2
```

## 🚀 v3.1.2 – Performance & Batch Parsing Edition

This is a major feature release of ap_ds, focusing on **batch parsing performance** and **Python 3.15 free-threading support**.

### ✨ New Features

#### 📦 Batch Parsing API (Brand New)

| API | Description |
|-----|-------------|
| `batch_get_metadata()` | Batch parse audio files, returns full metadata list |
| `batch_get_duration()` | Batch get audio durations, returns `{path: duration}` |
| `batch_get_metadata_by_type()` | Filter batch parsing by format (e.g., parse only MP3) |

#### 🧩 DAP Deduplication Optimization

`_add_to_dap_recordings()` upgraded from O(n) linear scan to **O(1) set-based deduplication**, delivering significant performance gains for large playlists.

#### 🐍 Python 3.15t Free-Threading Support

- **True GIL-less parallelism**: Batch parsing scales linearly on multi-core CPUs
- **Runtime self-check**: Automatically detects GIL status on import and provides clear prompts

**Full-performance users will see:**
```
🎉 ap_ds: GIL disabled (free-threading mode)
```

**Non-full-performance users will see:**
```
⚠️ ap_ds: GIL is enabled (multi-core parallelism limited).
   For full performance, upgrade to Python 3.15t
```

#### ⚙️ New Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `AP_DS_SUPPRESS_WARNINGS` | Not set | Set to `1` to suppress downgrade warnings |
| `AP_DS_SHOW_CONGRATS` | Not set | Set to `0` to hide congratulations message |

#### 🚀 Startup Acceleration

Python 3.15+ users automatically benefit from `lazy import` – heavy modules are loaded on demand, significantly speeding up `import ap_ds`.


## ⚡ Performance Comparison

### Test Environment

| Item | v3.0.0 LTS | v vv3.1.2/  | Mutagen |
|------|------------|--------|---------|
| **Python Version** | 3.13.4 (with GIL) | 3.15.0b4 (without GIL) | 3.13.4 (with GIL) |
| **Concurrency Method** | ThreadPoolExecutor | ProcessPoolExecutor | Single-threaded |
| **GIL Status** | ✅ Enabled | ❌ Disabled | ✅ Enabled |
| **Batch Parsing** | ❌ No | ✅ Yes | ❌ No |
| **Test Files** | 120 MP3s | 120 MP3s | 120 MP3s |


### 📊 Comparison 1: ap_ds v3.0.0 vs v vv3.1.2/  (120 files)

| Test | v3.0.0 (with GIL) | v vv3.1.2 (without GIL) | Conclusion |
|------|-------------------|----------------------|------------|
| **Serial parsing** | 0.848s | 1.367s |  vv3.1.2 slightly slower (GIL-less overhead) |
| **2-way concurrency** | 0.789s (1.08x) | 0.795s (1.72x) | ✅  vv3.1.2 begins to accelerate |
| **4-way concurrency** | 1.863s (0.46x) ❌ | 0.471s (2.90x) | 🚀  vv3.1.2 is 3.95x faster |
| **8-way concurrency** | 1.285s (0.66x) ❌ | **0.331s (4.13x)** | 🚀  vv3.1.2 is 3.88x faster |
| **Best time** | 0.789s (2 threads) | **0.331s (8 processes)** | 🚀 ** vv3.1.2 is 2.38x faster** |

**Key Findings:**

- **v3.0.0 (with GIL)**: Multi-threading is completely ineffective – 4 threads are twice as slow as serial (1.86s), and 8 threads achieve only 0.66x speedup
- **v vv3.1.2/  (without GIL)**: True multi-process parallelism – 8 processes achieve 4.13x speedup, reducing 120 files from 1.37s to 0.33s

> 💡 v3.0.0's multi-threading is bottlenecked by the GIL – more threads actually slow things down. v vv3.1.2 uses ProcessPoolExecutor for true parallelism, fully unleashing multi-core performance!


### 📊 Comparison 2: ap_ds v vv3.1.2/  vs Mutagen (120 files)

**Why compare with Mutagen?**

Mutagen is the most popular audio metadata library in the Python ecosystem, widely used in music players, tag editors, and more. However, it has the following limitations:

- ❌ **Single-threaded design**: All parsing tasks run serially, unable to utilize multi-core CPUs
- ❌ **No built-in batch concurrency**: Users must implement their own multi-threading/multi-processing wrappers
- ❌ **No Python 3.15t support**: Mutagen hasn't adapted to free-threading mode – the GIL limitation remains
- ❌ **No built-in batch APIs**: Processing large numbers of files requires manual loops, making code cumbersome

**These pain points are exactly what inspired ap_ds v vv3.1.2's batch parsing functionality.**

| Comparison | Mutagen (3.13.4) | ap_ds v vv3.1.2 (3.15t) | Advantage |
|------------|------------------|----------------------|-----------|
| **Parsing time** | 0.973s | **0.331s** | 🚀 ap_ds **2.94x faster** |
| **Concurrency** | Single-threaded serial | 8-process parallel | ✅ True parallelism |
| **Per-file average** | 0.0081s | **0.00275s** | 🚀 ap_ds **2.95x faster** |
| **GIL limitation** | ❌ Yes | ✅ No | ✅ ap_ds unconstrained |
| **Batch API** | ❌ No | ✅ `batch_get_metadata()` | ✅ Ready to use |
| **Multi-core utilization** | ❌ Single core | ✅ All cores | ✅ Full CPU utilization |


### 📈 Comprehensive Comparison Chart

```
                   120 MP3 File Parsing Time
                    
Mutagen (single-thread) ████████████████████████████████████ 0.973s
v3.0.0 (8 threads)      ████████████████████████████████████ 1.285s  ❌ Slower
v vv3.1.2 (serial)         ████████████████████████████████████████ 1.367s
v vv3.1.2 (8 processes)    ████████████ 0.331s  🚀 2.94x faster!
```


### 🎯 Summary

| Solution | 120 files time | Speedup | Recommended Use Case |
|----------|----------------|---------|---------------------|
| Mutagen (single-thread) | 0.973s | 1.00x | Small number of files |
| v3.0.0 (8 threads) | 1.285s | 0.66x ❌ | Not recommended for concurrency |
| v vv3.1.2 (serial) | 1.367s | 1.00x | Small number of files |
| **v vv3.1.2 (8 processes)** | **0.331s** | **4.13x** | **Large batches** 🚀 |

> **Conclusion: v vv3.1.2 + Python 3.15t + 8-process batch parsing = 120 MP3 files in just 0.33 seconds – 3x faster than Mutagen and 4x faster than v3.0.0 multi-threading!**


## 📦 Version Relationship

| Version | Type | Support Period | Use Case |
|---------|------|----------------|----------|
| **v3.0.0 LTS** | Long-Term Support | Until March 2031 | Production environments |
| **v vv3.1.2** | Feature Release | ~6 months | Early adopters / batch parsing needs |


## 🌐 apds.top is Now Live!

**The official ap_ds project homepage is now live with TLS encryption!**

🎉 Visit: **https://apds.top**

### Website Features

- 📄 **Complete Documentation**: API reference, user guides, FAQs
- 📦 **Release Distribution**: All version download links and changelogs
- 🔗 **Repository Navigation**: GitCode (primary), Gitee (China mirror)
- ✉️ **Feedback System**: Users can submit feedback directly via the website
- 🔒 **Full-site TLS Encryption**: All pages served over HTTPS


## 📌 Version Upgrade Recommendations

| User Type | Recommendation |
|-----------|----------------|
| Production environment | Continue using **v3.0.0 LTS**, wait for v4.0.0 LTS |
| Development/Testing | Upgrade to **v vv3.1.2** to experience new features |
| Need batch parsing | **Must upgrade** to v vv3.1.2 |
| Python 3.15t users | **Must upgrade** to v vv3.1.2 to leverage GIL-less advantages |

```bash
pip install --upgrade ap_ds
```


## ⚠️ Repository Migration Notice

### GitHub Deprecation, GitLab Abandonment, Migration to GitCode – apds.top Becomes the Permanent Home

This section explains in detail the history of ap_ds's official code repository migrations and final destination.

#### 1. Why GitHub Was Deprecated

The developer's GitHub account was locked due to the loss of two-factor authentication (2FA) devices. After multiple attempts to contact GitHub support, only automated bot replies were received. Due to the complete lack of human assistance, the developer decided to permanently abandon that GitHub account and will not create a new one in the foreseeable future. The old `dvs-web/ap_ds` repository is now officially deprecated and will no longer receive any updates.

#### 2. Why GitLab (JiHu) Was Abandoned

Following the GitHub issue, the project migrated its primary repository to Gitee and GitLab (JiHu). However, GitLab was recently abandoned due to platform policy changes that made basic account login a paid feature. Since the project relies on free open-source collaboration, this change created an unacceptable barrier for contributors and users. The developer attempted to find alternatives but found none within the platform's free tier. Therefore, the GitLab repository is no longer actively maintained.

#### 3. Why Gitee Is Now a Backup (Not Primary)

Gitee is an excellent platform, especially for developers within China, offering fast and stable access. It remains a strongly recommended choice. However, its role has been adjusted to backup or China-facing mirror for two primary reasons:

- **International Accessibility**: Gitee's servers are primarily located within China. For developers outside mainland China, access can be slow, unstable, and in some cases, completely blocked due to international network policies. This creates a poor experience for a significant portion of the user base.

- **User Interface and Workflow**: While functional, Gitee's UI and workflow are often considered outdated and less aligned with the modern Git workflows many international developers are accustomed to.

For these reasons, while Gitee is by no means "bad" and will continue to be fully supported as a China-facing mirror, it is no longer suitable as the sole primary repository for a project with a global audience.

#### 4. The Solution: GitCode Becomes the New Primary Repository

After evaluating the landscape of free Git hosting platforms, **GitCode** emerged as the ideal solution. GitCode offers a modern interface, a robust feature set, and most importantly, excellent accessibility for both domestic and international developers. It has become the new primary official repository for the ap_ds project.

**The new primary repository is located at:** [https://gitcode.com/dvsxt/ap_ds](https://gitcode.com/dvsxt/ap_ds)

#### 5. The Permanent Home: apds.top

**apds.top is now officially live and fully operational!**

This is not just another repository mirror – it is the **permanent official home** of ap_ds. It resolves all platform dependency issues:

- ✅ **Full independent control**: No longer affected by third-party platform policy changes
- ✅ **TLS encryption**: Full-site HTTPS secure access
- ✅ **Permanent stability**: Even if all third-party platforms fail, apds.top remains available
- ✅ **One-stop service**: Documentation, downloads, feedback, and repository navigation all integrated

**Official Project Homepage:** [https://apds.top](https://apds.top)

#### 6. Final Repository Strategy

| Platform | Status | Purpose |
|----------|--------|---------|
| **apds.top** | ✅ **Permanent Home** | Official source, documentation, downloads |
| **GitCode** | ✅ Primary Mirror | Code hosting for global users |
| **Gitee** | ✅ China Mirror | Fast access for China-based users |
| **GitHub** | ❌ Deprecated | No longer maintained |
| **GitLab (JiHu)** | ❌ Abandoned | No longer maintained |

> **Note for PyPI users**: The project description on PyPI will be updated with the v vv3.1.2 release. For the latest information, always refer to [https://apds.top](https://apds.top).


## Overview

ap_ds is a lightweight (2.5MB) Python audio library for playing and high-precision metadata parsing of MP3, FLAC, OGG, and WAV files. It has zero external Python dependencies, using only the Python standard library, and provides non-blocking playback suitable for GUI applications.

**Core Features:**
- **Extremely lightweight:** 2.5MB on Windows / 3.36MB on macOS complete solution
- **Zero Python dependencies:** Uses only the standard library
- **High-precision metadata:** WAV/FLAC 100%, OGG 99.99%, MP3 >98%
- **Batch parsing:** Process hundreds of files in parallel using `batch_get_metadata()`
- **Non-blocking playback:** Perfect for GUI applications
- **Cross-platform:** Windows, macOS, Linux, embedded ARM64
- **DAP recording system:** Automatic playback history with metadata only
- **Python 3.15t support:** GIL-less true parallelism, full multi-core performance
- **LTS support:** First long-term support version with 5-year maintenance commitment


---
## Contact & Support

### 📧 Licensing Inquiries
me@dvsyun.top or dvs6666@163.com · Response within 7 business days

### 🛠️ Technical Support
apds.top Issues · GitCode Issues · Gitee Issues · Email (free during LTS period)


---
## Official Repositories & Project Sources

**✅ Official Primary Repository (First Recommendation):** [https://apds.top](https://apds.top) – ap_ds's permanent official home, fully independently controlled, TLS encrypted, unaffected by third-party platform policies.

**✅ Primary Mirror (Global Access):** [GitCode](https://gitcode.com/dvsxt/ap_ds) – Globally accessible, modern UI, actively maintained as a public mirror.

**✅ China Mirror (Fast & Stable):** [Gitee](https://gitee.com/dssxt/ap_ds) – Full mirror for China-based developers, fast access, classic stable UI.

**❌ Deprecated & Abandoned:**
- **[GitHub (dvs-web/ap_ds)](https://github.com/dvs-web/ap_ds)** – Deprecated due to permanent account lockout, no longer maintained.
- **[GitLab (JiHu)](https://jihulab.com/dvs/ap_ds)** – Abandoned due to platform policy changes (basic account login became a paid feature), no longer maintained.

> **ℹ️ ap_ds v3.0.0 LTS – First Long-Term Support Release**  
> This version consolidates all previous improvements, adds deterministic resource cleanup, hash-verified downloads, and a 5-year support commitment. The old GitHub repository (`dvs-web/ap_ds`) is deprecated and no longer updated. The GitLab repository has been abandoned. For future updates and contributions, please use the official primary repository **[apds.top](https://apds.top)** or the public mirrors **GitCode** and **Gitee**.
>
> **Developer Personal Homepage & Blog:** [https://dvsx.top](https://dvsx.top) – Blog is currently undergoing maintenance and upgrades. Stay tuned.  
> **ap_ds Project Homepage:** [https://apds.top](https://apds.top) (Official documentation, releases, and license center).

**🔗 Canonical URL:** [https://apds.top/](https://apds.top/)  
**📖 Blog & Author:** [dvsx.top](https://dvsx.top) – Blog is currently undergoing maintenance and upgrades. Stay tuned.


---
## About the Author

**Developer:** Dvs (DvsXT)  
**Personal Homepage & Blog:** [https://dvsx.top](https://dvsx.top) – Blog is currently undergoing maintenance and upgrades. Stay tuned  
**Author Bio:** [https://dvsyun.top/me/dvs](https://dvsyun.top/me/dvs)  
**Email:** me@dvsyun.top · dvs6666@163.com


## ap_ds Official Portal

**🎵 ap_ds Official Website (Primary):** [https://apds.top](https://apds.top) – Permanent official home, complete documentation, releases, license center  
**📦 PyPI Project Page:** [https://pypi.org/project/ap_ds/](https://pypi.org/project/ap_ds/) – Installable via pip  
**🌐 Mirror Documentation Site:** [https://www.dvsyun.top/ap_ds](https://www.dvsyun.top/ap_ds) – Backup documentation access

> 👉 The ap_ds project homepage ([apds.top](https://apds.top)) is the **first recommendation** for official sources, hosting complete documentation, license details, version changelogs, and official releases. The author's personal blog ([dvsx.top](https://dvsx.top)) is currently undergoing maintenance and upgrades. Stay tuned.


## Let's Get Started!

### Installation

```bash
pip install ap_ds
```

Upgrade from an older version:

```bash
pip install --upgrade ap_ds
```


### 💡 Why Python 3.15t / 3.14t?

Python's **Free-Threading version** (filename with `t`) **removes the GIL (Global Interpreter Lock)** enabling true multi-core parallelism. Combined with ap_ds v vv3.1.2's batch parsing, **120 MP3 files can be parsed in just 0.33 seconds**.

> ⚠️ **Version Selection Note**: Python 3.15t is currently a beta release (b4) and may have unknown issues. For a more stable environment, we recommend Python 3.14t (stable). Both support GIL-less free-threading mode.

#### Windows Users

**Python 3.14t (Stable) Downloads:**

| Architecture | Download Link |
|--------------|---------------|
| Windows 64-bit | [python-3.14.4t-amd64.zip](https://mirrors.huaweicloud.com/python/3.14.4/python-3.14.4t-amd64.zip) |
| Windows 32-bit | [python-3.14.4t-win32.zip](https://mirrors.huaweicloud.com/python/3.14.4/python-3.14.4t-win32.zip) |
| ARM64 | [python-3.14.4t-arm64.zip](https://mirrors.huaweicloud.com/python/3.14.4/python-3.14.4t-arm64.zip) |

**Python 3.15t (Beta) Downloads:**

| Architecture | Download Link |
|--------------|---------------|
| Windows 64-bit | [python-3.15.0b4t-amd64.zip](https://mirrors.huaweicloud.com/python/3.15.0/python-3.15.0b4t-amd64.zip) |
| Windows 32-bit | [python-3.15.0b4t-win32.zip](https://mirrors.huaweicloud.com/python/3.15.0/python-3.15.0b4t-win32.zip) |
| ARM64 | [python-3.15.0b4t-arm64.zip](https://mirrors.huaweicloud.com/python/3.15.0/python-3.15.0b4t-arm64.zip) |

> 📦 **ZIP – Extract and Use**: Download and extract to any directory, add the `python.exe` path to your system PATH, and you're ready to go. No need to run an EXE installer – deployment takes seconds.

#### Linux Users

**Option 1: Using Package Manager**

**Fedora:**
```bash
sudo dnf install python3.14-freethreading
```
After installation, the interpreter is located at `/usr/bin/python3.14t`.

**Ubuntu/Debian (using deadsnakes PPA):**
```bash
sudo add-apt-repository ppa:deadsnakes
sudo apt-get update
sudo apt-get install python3.14-nogil
```
> This PPA provides the `-nogil` version, which is also a GIL-disabled build.

**Option 2: Using Conda (Cross-platform)**

Install from the `conda-forge` channel:
```bash
conda create -n nogil -c conda-forge python-freethreading
mamba create -n nogil -c conda-forge python-freethreading
```

**Option 3: Compiling from Source (General Method)**

```bash
# Download Python 3.14 source
wget https://www.python.org/ftp/python/3.14.0/Python-3.14.0.tgz
tar -xzf Python-3.14.0.tgz
cd Python-3.14.0

# Configure: --disable-gil is the key parameter
./configure --disable-gil

# Compile and install
make -j$(nproc)
sudo make install
```

#### macOS Users

**Option 1: Official Installer (Graphical)**

1. Download the macOS installer package from [python.org](https://www.python.org/downloads/)
2. Run the installer, click the **"Customize"** button on the "Installation Type" screen
3. In the component list that appears, **check the "Free-threaded Python"** option, and continue with the installation

**Option 2: Using Homebrew**

```bash
brew install python-freethreading
```
After installation, the interpreter is located at `$(brew --prefix)/bin/python3.14t`.

#### Verify Installation

Run the following commands to verify that the free-threading version is working correctly:

```bash
# Check version information (should include "free-threading build")
python3.14t --version

# Check GIL status (output False means GIL is disabled)
python3.14t -c "import sys; print(sys._is_gil_enabled())"
```

#### Create a Virtual Environment

```bash
python3.14t -m venv my_env
source my_env/bin/activate  # Linux/macOS
my_env\Scripts\activate     # Windows
```

> 💡 **Tip**: Using `python3.14t -m venv` creates a GIL-less isolated environment.


### Quick Start

```python
from ap_ds import AudioLibrary

# Initialize the library
lib = AudioLibrary()

# Play an audio file
aid = lib.play_from_file("music/song.mp3")

# Control playback
lib.pause_audio(aid)      # Pause
lib.play_audio(aid)       # Resume
lib.seek_audio(aid, 30.5) # Seek to 30.5 seconds

# Stop and get the played duration
duration = lib.stop_audio(aid)
print(f"Played {duration:.2f} seconds")
```


### 🚀 Batch Parsing (New in v vv3.1.2)

```python
from ap_ds import batch_get_metadata

# Batch parse an entire folder (120 MP3s in just 0.33 seconds!)
results = batch_get_metadata("/music/playlist/", max_workers=8)

for meta in results:
    print(f"{meta['path']}: {meta['duration']}s, {meta['bitrate']}bps")
```

**Batch Parsing APIs at a Glance:**

| API | Description |
|-----|-------------|
| `batch_get_metadata()` | Batch parse, returns full metadata list |
| `batch_get_duration()` | Batch get durations, returns `{path: duration}` |
| `batch_get_metadata_by_type()` | Filter batch parsing by format |


### DAP Playlist System

Audio files are automatically recorded to DAP (Dvs Audio Playlist) when played:

```python
# Files are automatically recorded
aid1 = lib.play_from_file("song1.mp3")
aid2 = lib.play_from_file("song2.ogg")

# Get all recordings
recordings = lib.get_dap_recordings()
print(f"Recorded {len(recordings)} files")

# Save as JSON
success = lib.save_dap_to_json("my_playlist.ap_ds-dap")
```

DAP stores only metadata (path, duration, bitrate, channels), not audio data. Each record uses approximately 150 bytes of memory.


### Platform Support

#### Windows
- Automatically downloads SDL2.dll and SDL2_mixer.dll with hash verification
- No manual configuration required
- Supports Windows 7 and above

#### macOS
- Automatically downloads SDL2.framework and SDL2_mixer.framework with hash verification
- No manual configuration required
- Supports macOS 10.9 and above

#### Linux
**Intelligent multi-layer import system:**

1. **System library check:** Uses system-installed SDL2 libraries
2. **User configuration:** Checks paths saved from previous runs
3. **Automatic installation:** Detects package manager and installs required packages
4. **Interactive guidance:** Provides manual options if all above fail

**Package manager support:**

```bash
# Ubuntu/Debian
sudo apt-get install libsdl2-dev libsdl2-mixer-dev

# Fedora
sudo dnf install SDL2-devel SDL2_mixer-devel

# Arch
sudo pacman -S sdl2 sdl2_mixer
```

#### Embedded ARM64
Tested on:
- **Orange Pi 4 Pro** (Allwinner A733, 2xA76 + 6xA55 @ 2.0GHz)
- **Raspberry Pi 5** (BCM2712, 4xA76 @ 2.4GHz)

Both run Ubuntu 22.04 with full audio functionality via 3.5mm output. Memory growth after extensive testing: ~4MB.


# ap_ds Audio Library – Complete API Reference

**Version:  vv3.1.2**  
**Documentation Date: July 2026**  
**Project Homepage: https://apds.top**


## Table of Contents

1. AudioLibrary Class – Complete API
   - Initialization
   - Playback Methods
   - Control Methods
   - Volume Methods
   - Fade & Transition Methods
   - Metadata Methods
   - Batch Parsing Methods (New in v vv3.1.2)
   - DAP System Methods
   - Resource Management
   - Internal Helper Methods

2. Top-Level Convenience Functions

3. AudioParser Class – Metadata API

4. AudioInfo Module – Format-Specific Parsers

5. SDL2 Integration Layer

6. Constants Reference

7. Exception Handling


## AudioLibrary Class – Complete API

The AudioLibrary class is the main interface for audio playback, control, and metadata extraction. It manages all SDL2 resources, provides non-blocking playback, and maintains the DAP (Dvs Audio Playlist) recording system.


### Initialization

#### `__init__(frequency: int = 44100, format: int = MIX_DEFAULT_FORMAT, channels: int = 2, chunksize: int = 2048) -> None`

**Description:**  
Initializes the SDL2 audio subsystem and SDL2_mixer library. This method must be called before any playback operations. It sets up the audio device with the specified parameters and registers an exit handler for automatic resource cleanup.

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `frequency` | `int` | `44100` | Audio sample rate (Hz). Common values: 44100 (CD quality), 48000 (DVD/video), 22050 (voice). Higher values improve quality but consume more CPU. |
| `format` | `int` | `MIX_DEFAULT_FORMAT` | Audio sample format. Typically `AUDIO_S16SYS` (16-bit signed, system endianness). See constants reference for alternatives. |
| `channels` | `int` | `2` | Number of audio channels. `1` = mono, `2` = stereo. Mono files will be automatically upmixed to stereo if the device is configured for stereo. |
| `chunksize` | `int` | `2048` | Buffer size in samples. Larger values reduce CPU usage but increase latency. For real-time applications, smaller values (1024) may be better. |

**Raises:**
- `RuntimeError`: If SDL2 initialization fails (e.g., no audio device available).
- `RuntimeError`: If mixer initialization fails (e.g., unsupported format).

**Example:**
```python
from ap_ds import AudioLibrary

# Default configuration (CD quality, stereo, low latency)
lib = AudioLibrary()

# Custom configuration for voice playback
lib_voice = AudioLibrary(frequency=22050, channels=1, chunksize=1024)
```

**Internal Behavior:**
1. Calls `SDL_Init(SDL_INIT_AUDIO)` to initialize the audio subsystem.
2. Calls `Mix_OpenAudio(frequency, format, channels, chunksize)` to open the mixer.
3. Registers `self.cleanup_function` with `atexit` to ensure resource cleanup.
4. Initializes internal data structures:
   - `_audio_cache: Dict[str, Mix_Chunk]` – Cached sound effects
   - `_music_cache: Dict[str, Mix_Music]` – Cached music tracks
   - `_channel_info: Dict[int, Dict]` – Active playback sessions
   - `_aid_to_filepath: Dict[int, str]` – AID to file path mapping
   - `_aid_counter: int` – Sequential AID generator
   - `_dap_recordings: List[Dict]` – DAP history list


### Playback Methods

#### `play_from_file(file_path: str, loops: int = 0, start_pos: float = 0.0) -> int`

**Description:**  
Loads and plays an audio file directly from disk. The file is loaded into memory and played immediately. For `.ap-ds-dap` files, this method records metadata but does not play audio (DAP files contain metadata only).

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_path` | `str` | Required | Full path to the audio file. Supported formats: MP3, WAV, FLAC, OGG |
| `loops` | `int` | `0` | Number of loops after the first play. `0` = play once, `-1` = loop indefinitely. |
| `start_pos` | `float` | `0.0` | Starting position in seconds. May not be supported for sound effects (short WAV files). |

> **Note:** AAC files support metadata parsing but cannot be played or have other operations performed on them due to SDL2 limitations.

**Returns:**  
`int` – A unique Audio ID (AID) identifying this playback instance. This ID can be used with all control methods.

**Raises:**
- `FileNotFoundError`: If the file does not exist.
- `RuntimeError`: If the file format is unsupported or the audio cannot be loaded.
- `RuntimeError`: If playback fails (e.g., no available channel).

**Behavior by File Type:**

| File Type | Mode | Seek Support | Fade Support |
|-----------|------|--------------|--------------|
| MP3, OGG, FLAC | Music (Mix_PlayMusic) | Yes | Yes |
| WAV (duration ≥ threshold) | Music (Mix_PlayMusic) | Yes | Yes |
| WAV (duration < threshold) | Sound effect (Mix_PlayChannel) | No | No |
| Other formats | Sound effect (Mix_PlayChannel) | No | No |

**Example:**
```python
# Play a song once from the beginning
aid = lib.play_from_file("song.mp3")

# Loop a short sound effect 5 times
aid = lib.play_from_file("beep.wav", loops=5)

# Start playing from 30 seconds in
aid = lib.play_from_file("podcast.mp3", start_pos=30.0)

# Loop indefinitely (background music)
aid = lib.play_from_file("ambient.ogg", loops=-1)
```

**Internal Workflow:**
1. Increments `_aid_counter` to generate a new AID.
2. Calls `_add_to_dap_recordings()` to record metadata (if available).
3. Determines playback mode via `_is_music_file()`.
4. For music files:
   - Calls `Mix_LoadMUS()` to load the file.
   - Calls `Mix_PlayMusic()` to start playback.
   - Stores the `Mix_Music` object in `_music_cache`.
   - Sets `channel = -1` (music uses a dedicated channel).
5. For sound effects:
   - Calls `Mix_LoadWAV()` to load the file.
   - Calls `Mix_PlayChannel(-1, audio, loops)` to play on the first available channel.
   - Stores the `Mix_Chunk` object in `_audio_cache`.
   - Returns the actual channel number.
6. Stores playback information in `_channel_info[channel]`.
7. If `start_pos > 0`, calls `_seek_audio()`.


#### `play_from_memory(file_path: str, loops: int = 0, start_pos: float = 0.0) -> int`

**Description:**  
Plays an audio file that has been preloaded into memory via `new_aid()`. This method is faster than `play_from_file()` for repeated plays of the same file since the file is already cached.

**Parameters:** Same as `play_from_file()`.

**Returns:** `int` – Audio ID (AID).

**Raises:**
- `ValueError`: If the file has not been preloaded via `new_aid()`.
- `RuntimeError`: If playback fails.

**Example:**
```python
# Preload sound effects
lib.new_aid("gunshot.wav")
lib.new_aid("explosion.wav")

# Play from memory (very fast, no disk I/O)
aid = lib.play_from_memory("gunshot.wav")
```

**Internal Workflow:**
1. Generates a new AID.
2. Records to DAP.
3. Checks if the file is in `_music_cache` or `_audio_cache`.
4. Plays from the cached object.
5. Records playback info in `_channel_info`.


#### `new_aid(file_path: str) -> int`

**Description:**  
Preloads an audio file into memory without playing it. This is useful for caching sound effects or music tracks that will be played multiple times. The file is loaded once and stored in the appropriate cache.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `file_path` | `str` | Path to the audio file to cache. |

**Returns:** `int` – Audio ID (AID) for the cached file.

**Raises:**
- `FileNotFoundError`: If the file does not exist.
- `RuntimeError`: If the file format is unsupported or loading fails.

**Example:**
```python
# Cache commonly used sounds
sounds = {
    'hit': lib.new_aid("hit.wav"),
    'jump': lib.new_aid("jump.wav"),
    'coin': lib.new_aid("coin.wav")
}

# Play from memory later
lib.play_from_memory(sounds['hit'])
```

**Internal Workflow:**
1. Increments AID counter.
2. Records to DAP.
3. Determines music vs sound effect via `_is_music_file()`.
4. If music:
   - Calls `Mix_LoadMUS()`.
   - Stores in `_music_cache[file_path]`.
5. If sound effect:
   - Calls `Mix_LoadWAV()`.
   - Stores in `_audio_cache[file_path]`.
6. Maps AID to file path in `_aid_to_filepath`.


### Control Methods

#### `play_audio(aid: int) -> None`

**Description:**  
Resumes playback of a paused audio instance. If the audio is not paused, this method has no effect.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `aid` | `int` | Audio ID returned by `play_from_file()`, `play_from_memory()`, or `new_aid()`. |

**Raises:**
- `ValueError`: If the AID is invalid or the audio is not paused.

**Example:**
```python
aid = lib.play_from_file("song.mp3")
time.sleep(2)
lib.pause_audio(aid)      # Pause after 2 seconds
time.sleep(1)
lib.play_audio(aid)       # Resume after 1 second of pause
```

**Internal Behavior:**
1. Finds the channel associated with the AID via `_find_channel_by_aid()`.
2. Retrieves playback info from `_channel_info`.
3. If audio is paused (`info['paused']` is `True`):
   - For music (`is_music` is `True`): Calls `Mix_ResumeMusic()`.
   - For sound effects: Calls `Mix_Resume(channel)`.
   - Resets `info['paused']` to `False`.
   - Adjusts `info['start_time']` to account for pause duration.


#### `pause_audio(aid: int) -> None`

**Description:**  
Pauses the audio instance specified by the AID. The audio can later be resumed with `play_audio()`.

**Parameters:** Same as `play_audio()`.

**Raises:**
- `ValueError`: If the AID is invalid or the audio is already paused.

**Example:**
```python
aid = lib.play_from_file("song.mp3")
time.sleep(5)
lib.pause_audio(aid)
```

**Internal Behavior:**
1. Finds the channel associated with the AID.
2. Retrieves playback info.
3. If not already paused:
   - For music: `Mix_PauseMusic()`.
   - For sound effects: `Mix_Pause(channel)`.
   - Sets `info['paused'] = True`.
   - Records `paused_position = time.time() - info['start_time']`.


#### `stop_audio(aid: int) -> float`

**Description:**  
Stops playback of the specified audio instance and releases associated resources. The audio cannot be resumed after stopping.

**Parameters:** Same as `play_audio()`.

**Returns:** `float` – Total playback duration in seconds before stopping.

**Raises:**
- `ValueError`: If the AID is invalid.

**Example:**
```python
aid = lib.play_from_file("song.mp3")
time.sleep(10)
duration = lib.stop_audio(aid)
print(f"Played {duration:.2f} seconds")
```

**Internal Behavior:**
1. Finds the channel associated with the AID.
2. Calculates `played_time` (current time minus start time, or paused position).
3. If music: `Mix_HaltMusic()`.
4. If sound effect: `Mix_HaltChannel(channel)`.
5. Removes the entry from `_channel_info`.
6. Returns `played_time`.


#### `seek_audio(aid: int, position: float) -> None`

**Description:**  
Seeks to a specific position (in seconds) within an audio track. This method only works for music-mode files (MP3, OGG, FLAC, and long WAV files). Sound effects (short WAVs) do not support seeking.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `aid` | `int` | Audio ID. |
| `position` | `float` | Target position in seconds. Must be between 0 and the total duration. |

**Raises:**
- `ValueError`: If the AID is invalid.
- `RuntimeError`: If the audio is not seekable (sound effect mode).

**Example:**
```python
# Seek to 30 seconds into the track
lib.seek_audio(aid, 30.0)
```

**Internal Behavior (Music):**
1. Stops current playback using `Mix_HaltMusic()`.
2. Reloads the music file using `Mix_LoadMUS()`.
3. Starts playback from the beginning using `Mix_PlayMusic()`.
4. If `Mix_SetMusicPosition` is available, seeks to the target position.
5. Updates `_channel_info` with the new start time.

**Internal Behavior (Sound Effect):**
1. Stops current playback using `Mix_HaltChannel()`.
2. Replays the cached chunk using `Mix_PlayChannel()`.
3. This effectively starts from the beginning; seeking is not supported.


### Volume Methods

#### `set_volume(aid: int, volume: int) -> bool`

**Description:**  
Sets the volume for the specified audio instance. Volume is scaled from 0 (mute) to 128 (maximum). The value is automatically clamped.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `aid` | `int` | Audio ID. |
| `volume` | `int` | Volume value (0–128). Values outside this range will be clamped. |

**Returns:** `bool` – `True` if the volume was set successfully, `False` otherwise.

**Example:**
```python
# Set volume to 50% (64 out of 128)
lib.set_volume(aid, 64)

# Mute
lib.set_volume(aid, 0)

# Maximum
lib.set_volume(aid, 128)
```

**Internal Behavior:**
1. Finds the channel associated with the AID.
2. Clamps volume to the range `[0, 128]`.
3. If music: Calls `Mix_VolumeMusic(volume)`.
4. If sound effect: Calls `Mix_Volume(channel, volume)`.


#### `get_volume(aid: int) -> int`

**Description:**  
Returns the current volume of the specified audio instance.

**Parameters:** Same as `set_volume()`.

**Returns:** `int` – Current volume (0–128). Returns `0` if the AID is invalid.

**Example:**
```python
current = lib.get_volume(aid)
print(f"Current volume: {current}")
```

**Internal Behavior:**
1. Finds the channel associated with the AID.
2. If music: Calls `Mix_VolumeMusic(-1)` (`-1` means get without setting).
3. If sound effect: Calls `Mix_Volume(channel, -1)`.


### Fade & Transition Methods

#### `fadein_music(aid: int, loops: int = -1, ms: int = 0) -> bool`

**Description:**  
Fades in music over the specified duration. This method stops any currently playing music and starts the specified track with a fade-in effect. Only works for music-mode files.

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `aid` | `int` | Required | Audio ID of the music file. |
| `loops` | `int` | `-1` | Number of loops. `-1` = infinite, `0` = once, `n` = n times. |
| `ms` | `int` | `0` | Fade-in duration in milliseconds. `0` means no fade-in (start immediately). |

**Returns:** `bool` – `True` if fade-in started successfully, `False` otherwise.

**Example:**
```python
# Fade in over 2 seconds
lib.fadein_music(aid, loops=-1, ms=2000)

# Fade in with no looping (play once)
lib.fadein_music(aid, loops=0, ms=500)
```

**Internal Behavior:**
1. Searches `_channel_info` for the AID and verifies it's music.
2. Calls `_add_to_dap_recordings()` (records to DAP).
3. Loads the music file if not already cached.
4. Stops any current music with `Mix_HaltMusic()`.
5. Calls `Mix_FadeInMusic(music, loops, ms)`.
6. If successful, updates `_channel_info` with the new start time.


#### `fadein_music_pos(aid: int, loops: int = -1, ms: int = 0, position: float = 0.0) -> bool`

**Description:**  
Fades in music starting from a specific position. This is useful for resuming playback from a saved timestamp.

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `aid` | `int` | Required | Audio ID. |
| `loops` | `int` | `-1` | Number of loops. |
| `ms` | `int` | `0` | Fade-in duration in milliseconds. |
| `position` | `float` | `0.0` | Starting position in seconds. |

**Returns:** `bool` – `True` if successful, `False` otherwise.

**Example:**
```python
# Fade in over 1 second starting from the 30-second mark
lib.fadein_music_pos(aid, loops=-1, ms=1000, position=30.0)
```

**Internal Behavior:**
1. Checks if `Mix_FadeInMusicPos` is available in the loaded SDL2_mixer.
2. Same as `fadein_music()` but calls `Mix_FadeInMusicPos()` with the position parameter.


#### `fadeout_music(ms: int = 0) -> bool`

**Description:**  
Fades out the currently playing music over the specified duration.

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `ms` | `int` | `0` | Fade-out duration in milliseconds. `0` means stop immediately. |

**Returns:** `bool` – `True` if fade-out started, `False` if no music is playing.

**Example:**
```python
# Smooth fade out over 3 seconds
lib.fadeout_music(ms=3000)

# Stop immediately
lib.fadeout_music(ms=0)
```

**Internal Behavior:**
1. Calls `Mix_FadeOutMusic(ms)`.
2. Returns `True` if the return value is `1` (success), `False` otherwise.


#### `is_music_playing() -> bool`

**Description:**  
Checks if any music is currently playing.

**Returns:** `bool` – `True` if music is playing, `False` otherwise.

**Example:**
```python
if lib.is_music_playing():
    print("Music is playing")
else:
    print("Music is stopped or paused")
```

**Internal Behavior:** Calls `Mix_PlayingMusic()`.


#### `is_music_paused() -> bool`

**Description:**  
Checks if music is paused.

**Returns:** `bool` – `True` if paused, `False` otherwise.

**Internal Behavior:** Calls `Mix_PausedMusic()`.


#### `get_music_fading() -> int`

**Description:**  
Returns the current fade status of the music.

**Returns:** `int` – One of:
- `0` (`MUS_NO_FADING`): No fade in progress.
- `1` (`MUS_FADING_OUT`): Fading out.
- `2` (`MUS_FADING_IN`): Fading in.

**Example:**
```python
state = lib.get_music_fading()
if state == 1:
    print("Fading out...")
```

**Internal Behavior:** Calls `Mix_FadingMusic()`.


### Metadata Methods

#### `get_audio_duration(source: Union[str, int], is_file: bool = False) -> Union[int, Tuple[int, str]]`

**Description:**  
Returns the duration of an audio file in seconds. Can accept either a file path (string) or an AID (integer).

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `source` | `str` or `int` | Required | File path or AID. |
| `is_file` | `bool` | `False` | If `True`, treats `source` as a file path; if `False`, treats it as an AID. |

**Returns:**
- On success: `int` – Duration in seconds (floor).
- On failure: `Tuple[int, str]` – `(0, error_message)`.

**Example:**
```python
# Via file path
duration = lib.get_audio_duration("song.mp3", is_file=True)

# Via AID
aid = lib.play_from_file("song.mp3")
duration = lib.get_audio_duration(aid, is_file=False)
```

**Internal Behavior:**
1. If `is_file` is `True`, calls `_get_duration_by_filepath(str(source))`.
2. If `is_file` is `False`, looks up the file path from `_aid_to_filepath`, then calls `_get_duration_by_filepath()`.


#### `get_audio_metadata(source: Union[str, int], is_file: bool = False) -> Optional[Dict]`

**Description:**  
Returns full metadata for an audio file: duration, sample rate, channels, bitrate, and format.

**Parameters:** Same as `get_audio_duration()`.

**Returns:** `Dict` or `None` (if parsing fails).

**Dictionary Structure:**
```python
{
    'path': str,           # Full file path
    'format': str,         # File extension (mp3, wav, etc.)
    'duration': int,       # Duration in seconds (floor)
    'length': float,       # Exact duration in seconds (float)
    'sample_rate': int,    # Sample rate in Hz
    'channels': int,       # 1 (mono) or 2 (stereo)
    'bitrate': int         # Bitrate in bps
}
```

**Example:**
```python
metadata = lib.get_audio_metadata("song.flac", is_file=True)
if metadata:
    print(f"Sample rate: {metadata['sample_rate']} Hz")
    print(f"Bitrate: {metadata['bitrate'] / 1000:.0f} kbps")
```


#### `get_audio_metadata_by_path(file_path: str) -> Optional[Dict]`

**Description:**  
Convenience method equivalent to `get_audio_metadata(file_path, is_file=True)`.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `file_path` | `str` | Path to the audio file. |

**Returns:** `Dict` or `None`.


#### `get_audio_metadata_by_aid(aid: int) -> Optional[Dict]`

**Description:**  
Convenience method equivalent to `get_audio_metadata(aid, is_file=False)`.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `aid` | `int` | Audio ID. |

**Returns:** `Dict` or `None`.


### Batch Parsing Methods (New in v vv3.1.2)

#### `batch_get_metadata(file_paths: Union[List[str], str], max_workers: Optional[int] = None, show_progress: bool = False) -> List[Dict]`

**Description:**  
Parses multiple audio files in parallel using multi-processing. On Python 3.15t (GIL-less environment), this achieves true multi-core parallel acceleration.

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_paths` | `List[str]` or `str` | Required | List of file paths, or a single directory path string. If a directory is provided, it will be recursively scanned for all supported audio files. |
| `max_workers` | `Optional[int]` | `None` | Maximum number of worker processes. Defaults to the number of CPU cores. |
| `show_progress` | `bool` | `False` | If `True`, prints progress to stdout. |

**Returns:** `List[Dict]` – List of metadata dictionaries. Files that fail to parse are omitted.

**Performance Reference:**
- 120 MP3 files, 8-process parallel: **0.33 seconds**
- Speedup vs serial parsing: **4.13x**
- Compared to Mutagen (single-threaded): **2.94x faster**

**Example:**
```python
from ap_ds import AudioLibrary

lib = AudioLibrary()

# Batch parse an entire folder
results = lib.batch_get_metadata("/music/playlist/", max_workers=8, show_progress=True)

for meta in results:
    print(f"{meta['path']}: {meta['duration']}s, {meta['bitrate']}bps")

# Parse a list of files
files = ["song1.mp3", "song2.flac", "song3.ogg"]
results = lib.batch_get_metadata(files)
```

**Internal Behavior:**
1. If `file_paths` is a directory path, recursively scans to collect all supported audio files (`.mp3`, `.wav`, `.flac`, `.ogg`, `.aac`).
2. Creates a process pool using `ProcessPoolExecutor`, assigning files to worker processes.
3. Each worker process calls `_parse_single_file()` to parse a single file.
4. Collects and returns all successfully parsed metadata.


#### `batch_get_duration(file_paths: Union[List[str], str], max_workers: Optional[int] = None) -> Dict[str, int]`

**Description:**  
Batch gets durations for multiple audio files.

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_paths` | `List[str]` or `str` | Required | List of file paths, or a single directory path string. |
| `max_workers` | `Optional[int]` | `None` | Maximum number of worker processes. Defaults to the number of CPU cores. |

**Returns:** `Dict[str, int]` – `{file_path: duration_in_seconds}` mapping. Files that fail to parse are omitted.

**Example:**
```python
durations = lib.batch_get_duration("/music/playlist/")
for path, duration in durations.items():
    print(f"{path}: {duration} seconds")
```


#### `batch_get_metadata_by_type(file_paths: Union[List[str], str], file_type: str, max_workers: Optional[int] = None) -> List[Dict]`

**Description:**  
Batch parses audio files but only returns results for the specified format.

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `file_paths` | `List[str]` or `str` | Required | List of file paths, or a single directory path string. |
| `file_type` | `str` | Required | File extension filter (e.g., `"mp3"`, `"flac"`). |
| `max_workers` | `Optional[int]` | `None` | Maximum number of worker processes. Defaults to CPU cores. |

**Returns:** `List[Dict]` – List of metadata dictionaries matching the specified format.

**Example:**
```python
# Parse only MP3 files
mp3_results = lib.batch_get_metadata_by_type("/music/", "mp3")
```


### DAP System Methods

#### `save_dap_to_json(save_path: str) -> bool`

**Description:**  
Saves all current DAP records in memory to a JSON file. The file extension must be `.ap-ds-dap`.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `save_path` | `str` | Output file path (must end with `.ap-ds-dap`). |

**Returns:** `bool` – `True` if successful, `False` otherwise.

**Raises:**
- `ValueError`: If the file extension is not `.ap-ds-dap`.

**Example:**
```python
if lib.save_dap_to_json("my_playlist.ap-ds-dap"):
    print("Playlist saved!")
else:
    print("Save failed")
```

**File Format:**
```json
[
  {
    "path": "/music/song1.mp3",
    "duration": 240,
    "bitrate": 320000,
    "channels": 2
  },
  {
    "path": "/music/song2.ogg",
    "duration": 180,
    "bitrate": 128000,
    "channels": 2
  }
]
```


#### `get_dap_recordings() -> List[Dict]`

**Description:**  
Returns a copy of all current DAP records in memory.

**Returns:** `List[Dict]` – List of record dictionaries.

**Example:**
```python
records = lib.get_dap_recordings()
for rec in records:
    print(f"{rec['path']}: {rec['duration']}s")
```


#### `clear_dap_recordings() -> None`

**Description:**  
Clears all DAP records from memory. This operation is irreversible unless you have previously saved the records.

**Example:**
```python
lib.clear_dap_recordings()
print(f"Remaining records: {len(lib.get_dap_recordings())}")  # 0
```


#### `_add_to_dap_recordings(file_path: str) -> None`

**Description:**  
Internal method that adds a file to the DAP recording list. Automatically called by `play_from_file()`, `play_from_memory()`, and `new_aid()`.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `file_path` | `str` | Path to the audio file. |

**Internal Behavior:**
1. Calls `get_audio_metadata_by_path(file_path)` to extract metadata.
2. If metadata is available, creates a record containing `path`, `duration`, `bitrate`, and `channels`.
3. Uses **O(1) set-based deduplication** (v vv3.1.2 optimization) to avoid duplicate records.


### Resource Management

#### `clear_memory_cache() -> None`

**Description:**  
Releases all cached audio data from memory (both sound effects and music). After calling this method, any subsequent playback will need to reload files from disk.

**Example:**
```python
lib.clear_memory_cache()
print("All cached audio cleared")
```

**Internal Behavior:**
1. Iterates through `_audio_cache` and calls `Mix_FreeChunk()` for each.
2. Iterates through `_music_cache` and calls `Mix_FreeMusic()` for each.
3. Clears both dictionaries.


#### `cleanup_function() -> None`

**Description:**  
Registered with `atexit` to clean up all resources when the Python interpreter exits. This method:
- Clears the memory cache.
- Closes the audio device with `Mix_CloseAudio()`.
- Shuts down SDL with `SDL_Quit()`.

Users should not call this method directly; it is automatically invoked during interpreter shutdown.


### Internal Helper Methods

These methods are for internal use but are documented here for completeness.

#### `_find_channel_by_aid(aid: int) -> Optional[int]`

**Description:**  
Searches `_channel_info` for the channel associated with the given AID.

**Returns:** `int` – Channel number, or `None` if not found.


#### `_get_file_path_by_aid(aid: int) -> Optional[str]`

**Description:**  
Searches `_channel_info` for the file path associated with the given AID.

**Returns:** `str` – File path, or `None` if not found.


#### `_is_music_file(file_path: str) -> bool`

**Description:**  
Determines whether a file should be treated as music (seekable) or sound effect (non-seekable). The decision is based on:
- File extension (`.mp3`, `.ogg`, `.flac` → music).
- For WAV files: compares duration against `WAV_THRESHOLD`.

**Returns:** `bool` – `True` for music mode, `False` for sound effect mode.


#### `_seek_audio(channel: int, position: float) -> None`

**Description:**  
Internal method that performs the actual seek operation on the given channel.


#### `_get_duration_by_filepath(file_path: str) -> Union[int, Tuple[int, str]]`

**Description:**  
Internal method that extracts duration using the `audio_parser`. Returns an integer (success) or a tuple `(0, error_message)` (failure).


#### `_get_file_duration(file_path: str) -> float`

**Description:**  
Internal method that returns duration as a float, defaulting to `0.0` on error.


## Top-Level Convenience Functions

ap_ds exports the following convenience functions directly at the package top level, allowing users to call them quickly without creating an `AudioLibrary` instance.

### `batch_get_metadata(file_paths, max_workers=None, show_progress=False) -> List[Dict]`

**Description:**  
Batch parses audio files. Equivalent to `AudioLibrary.batch_get_metadata()`.

**Example:**
```python
from ap_ds import batch_get_metadata

results = batch_get_metadata("/music/", max_workers=8)
```


### `batch_get_duration(file_paths, max_workers=None) -> Dict[str, int]`

**Description:**  
Batch gets audio durations. Equivalent to `AudioLibrary.batch_get_duration()`.

**Example:**
```python
from ap_ds import batch_get_duration

durations = batch_get_duration("/music/")
```


### `batch_get_metadata_by_type(file_paths, file_type, max_workers=None) -> List[Dict]`

**Description:**  
Filters batch parsing by format. Equivalent to `AudioLibrary.batch_get_metadata_by_type()`.

**Example:**
```python
from ap_ds import batch_get_metadata_by_type

mp3s = batch_get_metadata_by_type("/music/", "mp3")
```


### `is_full_performance() -> bool`

**Description:**  
Checks whether the current runtime is in full-performance mode (Python 3.15t + profiling available).

**Returns:** `bool` – `True` if in full-performance mode, `False` otherwise.

**Example:**
```python
from ap_ds import is_full_performance

if is_full_performance():
    print("🚀 Full-performance mode!")
```


### `get_runtime_info() -> dict`

**Description:**  
Gets runtime environment information.

**Returns:** `dict` – Diagnostic information including Python version, GIL status, profiling availability, CPU core count, etc.

**Example:**
```python
from ap_ds import get_runtime_info

info = get_runtime_info()
print(info["python_version"], info["gil_enabled"])
```


## AudioParser Class – Metadata API

The AudioParser class provides a unified interface for extracting metadata from audio files. It is used internally by AudioLibrary but can also be used directly.


### `get_audio_parser() -> AudioParser`

**Description:**  
Singleton factory function that returns an AudioParser instance. The same instance is reused across multiple calls.

**Note:** This function is located in the `ap_ds.audio_parser` submodule and must be imported from there.

**Example:**
```python
from ap_ds.audio_parser import get_audio_parser

parser = get_audio_parser()
metadata = parser.get_audio_metadata("song.mp3")
```


### `AudioParser.get_audio_metadata(file_path: str) -> Optional[Dict]`

**Description:**  
Returns metadata for the given audio file. Delegates to format-specific parsers in `audio_info.py`.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `file_path` | `str` | Path to the audio file. |

**Returns:** `Dict` or `None` (if parsing fails).

**Dictionary Structure:**
```python
{
    'path': file_path,
    'format': ext,           # e.g., 'mp3', 'wav'
    'duration': int(info.length),
    'length': float(info.length),
    'sample_rate': info.sample_rate,
    'channels': info.channels,
    'bitrate': info.bitrate
}
```


### `AudioParser.get_audio_duration(file_path: str) -> int`

**Description:**  
Returns the duration of an audio file in seconds (floor).

**Returns:** `int` – Duration in seconds, or `0` on error.


### `AudioParser.batch_get_metadata(file_paths, max_workers=None, show_progress=False) -> List[Dict]`

**Description:**  
Batch parsing method for the AudioParser class. Equivalent to the top-level `batch_get_metadata()` function.

**Example:**
```python
from ap_ds.audio_parser import get_audio_parser

parser = get_audio_parser()
results = parser.batch_get_metadata("/music/", max_workers=8)
```


### `AudioParser.batch_get_duration(file_paths, max_workers=None) -> Dict[str, int]`

**Description:**  
Batch get duration method for the AudioParser class. Equivalent to the top-level `batch_get_duration()` function.


### `AudioParser.batch_get_metadata_by_type(file_paths, file_type, max_workers=None) -> List[Dict]`

**Description:**  
Format-filtered batch parsing method for the AudioParser class. Equivalent to the top-level `batch_get_metadata_by_type()` function.


### Legacy Methods (Retained for Backward Compatibility)

These methods are aliases and behave identically to `get_audio_duration()`:

- `get_ogg_duration(file_path)`
- `get_flac_duration(file_path)`
- `get_mp3_duration(file_path)`
- `get_wav_duration(file_path)`
- `get_duration_by_extension(file_path)`


## AudioInfo Module – Format-Specific Parsers

The `audio_info.py` module contains low-level parsers for each supported format. Each parser returns a `StreamInfo` object with the following attributes:

- `length: float` – Duration in seconds (precise).
- `sample_rate: int` – Sample rate in Hz.
- `channels: int` – Number of audio channels.
- `bitrate: int` – Bitrate in bps.


### `StreamInfo` Class

**Attributes:**
- `length` (`float`) – Duration in seconds.
- `sample_rate` (`int`) – Sample rate in Hz.
- `channels` (`int`) – Number of channels (`1` = mono, `2` = stereo).
- `bitrate` (`int`) – Bitrate in bps.

**String Representation:** `"<StreamInfo length=3.141593s rate=44100Hz channels=2 bitrate=320000bps>"`


### `WAVFile` Class

**Parser:** Reads RIFF chunks, extracts `fmt` and `data` chunks.

**Accuracy:** 100% (based on file structure, no heuristics).

**Method:**
1. Reads `RIFF` header and `WAVE` identifier.
2. Scans `fmt` (format) and `data` chunks.
3. Extracts from `fmt`: `channels`, `sample_rate`, `block_align`.
4. Extracts from `data`: `data_size`.
5. Calculates `total_frames = data_size // block_align`.
6. Calculates `length = total_frames / sample_rate`.
7. Calculates `bitrate = sample_rate * block_align * 8 // channels`.


### `FLACFile` Class

**Parser:** Reads the `STREAMINFO` block (mandatory in all FLAC files).

**Accuracy:** 100% (from metadata).

**Method:**
1. Validates `fLaC` magic bytes.
2. Iterates through metadata blocks.
3. For block type `0` (`STREAMINFO`):
   - Extracts `sample_rate` from bytes 10–12.
   - Extracts `channels` from byte 12.
   - Extracts `total_samples` from bytes 13–17.
   - Calculates `length = total_samples / sample_rate`.
   - Calculates `bitrate = file_size * 8 / length`.
4. Returns `StreamInfo`.


### `MP3File` Class

**Parser:** Frame-by-frame scanner that counts frames and accumulates sample counts.

**Accuracy:** >98% (limited by variable bitrate and incomplete last frames).

**Method:**
1. Scans the file byte by byte looking for `0xFF` (frame sync).
2. Reads the next 3 bytes to form the frame header.
3. Extracts:
   - `bitrate` from bits 4–7 of byte 1.
   - `sample_rate` from bits 2–3 of byte 1.
4. Calculates frame length: `144000 * bitrate / sample_rate`.
5. Advances the file pointer by the frame length.
6. Increments `total_frames`.
7. After scanning, calculates:
   - `length = total_frames * 1152 / sample_rate` (1152 samples per MP3 frame).
   - `bitrate = file_size * 8 / length`.


### `OGGFile` Class

**Parser:** Reads Ogg pages, extracts granular position (total samples) from the last page.

**Accuracy:** 99.99% (granular position is precise, may be slightly off if file is truncated).

**Method:**
1. Scans Ogg pages (each begins with `OggS`).
2. Extracts granular position from bytes 6–13.
3. Keeps the maximum granular position (last page).
4. Also reads the first packet to extract `sample_rate` and `channels` from the Vorbis identification header.
5. Calculates `length = last_granule / sample_rate`.
6. Calculates `bitrate = file_size * 8 / length`.


### `AACFile` Class

**Parser:** Reads ADTS (Audio Data Transport Stream) frames, accumulates sample counts.

**Accuracy:** >99% (based on frame counting, similar to MP3).

**Method:**
1. Scans for `0xFFF` (ADTS sync word).
2. Reads the next 7 bytes as the header.
3. Extracts:
   - `sample_rate` from bits 2–5 of byte 2.
   - `channels` from bits 6–7 of byte 2 and byte 3.
   - `frame_length` from bytes 3–5.
4. Adds 1024 to `total_samples` (samples per AAC frame).
5. Jumps forward by `frame_length - 7`.
6. Calculates `length = total_samples / sample_rate`.
7. Calculates `bitrate = file_size * 8 / length`.


### `open_audio(filename: str) -> FileType`

**Description:**  
Factory function that returns an appropriate parser instance based on the file extension.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `filename` | `str` | Path to the audio file. |

**Returns:** An instance of `WAVFile`, `FLACFile`, `MP3File`, `OGGFile`, or `AACFile`.

**Raises:** `ValueError` if the format is unsupported.


## SDL2 Integration Layer

The `player.py` module contains low-level bindings to SDL2 and SDL2_mixer using `ctypes`. All functions have `argtypes` and `restype` properly set to ensure cross-platform stability.


### Global SDL2 Functions

| Function | C Binding | Description |
|----------|-----------|-------------|
| `SDL_Init(flags)` | `_sdl_lib.SDL_Init` | Initializes SDL subsystems. |
| `SDL_Quit()` | `_sdl_lib.SDL_Quit` | Shuts down SDL. |
| `SDL_GetError()` | `_sdl_lib.SDL_GetError` | Returns the last SDL error message. |
| `SDL_Delay(ms)` | `_sdl_lib.SDL_Delay` | Sleeps for the specified number of milliseconds. |
| `SDL_RWFromFile(file, mode)` | `_sdl_lib.SDL_RWFromFile` | Opens a file for reading using SDL's RWops. |


### Global SDL2_mixer Functions

| Function | C Binding | Description |
|----------|-----------|-------------|
| `Mix_OpenAudio(freq, format, channels, chunksize)` | `_mix_lib.Mix_OpenAudio` | Opens the audio device. |
| `Mix_CloseAudio()` | `_mix_lib.Mix_CloseAudio` | Closes the audio device. |
| `Mix_LoadWAV_RW(rwops, freesrc)` | `_mix_lib.Mix_LoadWAV_RW` | Loads a WAV file into a `Mix_Chunk`. |
| `Mix_LoadMUS_RW(rwops, freesrc)` | `_mix_lib.Mix_LoadMUS_RW` | Loads a music file into a `Mix_Music`. |
| `Mix_FreeChunk(chunk)` | `_mix_lib.Mix_FreeChunk` | Frees a `Mix_Chunk`. |
| `Mix_FreeMusic(music)` | `_mix_lib.Mix_FreeMusic` | Frees a `Mix_Music`. |
| `Mix_PlayChannel(channel, chunk, loops)` | `_mix_lib.Mix_PlayChannel` | Plays a chunk on a channel. |
| `Mix_PlayMusic(music, loops)` | `_mix_lib.Mix_PlayMusic` | Plays music. |
| `Mix_Pause(channel)` | `_mix_lib.Mix_Pause` | Pauses a channel. |
| `Mix_PauseMusic()` | `_mix_lib.Mix_PauseMusic` | Pauses music. |
| `Mix_Resume(channel)` | `_mix_lib.Mix_Resume` | Resumes a channel. |
| `Mix_ResumeMusic()` | `_mix_lib.Mix_ResumeMusic` | Resumes music. |
| `Mix_HaltChannel(channel)` | `_mix_lib.Mix_HaltChannel` | Stops a channel. |
| `Mix_HaltMusic()` | `_mix_lib.Mix_HaltMusic` | Stops music. |
| `Mix_Volume(channel, volume)` | `_mix_lib.Mix_Volume` | Sets/gets channel volume. |
| `Mix_VolumeMusic(volume)` | `_mix_lib.Mix_VolumeMusic` | Sets/gets music volume. |
| `Mix_Playing(channel)` | `_mix_lib.Mix_Playing` | Checks if a channel is playing. |
| `Mix_PlayingMusic()` | `_mix_lib.Mix_PlayingMusic` | Checks if music is playing. |
| `Mix_Paused(channel)` | `_mix_lib.Mix_Paused` | Checks if a channel is paused. |
| `Mix_PausedMusic()` | `_mix_lib.Mix_PausedMusic` | Checks if music is paused. |
| `Mix_FadeInMusic(music, loops, ms)` | `_mix_lib.Mix_FadeInMusic` | Fades in music. |
| `Mix_FadeInMusicPos(music, loops, ms, pos)` | `_mix_lib.Mix_FadeInMusicPos` | Fades in music from a position. |
| `Mix_FadeOutMusic(ms)` | `_mix_lib.Mix_FadeOutMusic` | Fades out music. |
| `Mix_FadingMusic()` | `_mix_lib.Mix_FadingMusic` | Returns fade status. |
| `Mix_SetMusicPosition(pos)` | `_mix_lib.Mix_SetMusicPosition` | Seeks within music (if supported). |


## Constants Reference

### SDL Initialization Flags

| Constant | Value | Description |
|----------|-------|-------------|
| `SDL_INIT_TIMER` | `0x00000001` | Timer subsystem. |
| `SDL_INIT_AUDIO` | `0x00000010` | Audio subsystem. |
| `SDL_INIT_VIDEO` | `0x00000020` | Video subsystem. |
| `SDL_INIT_JOYSTICK` | `0x00000200` | Joystick subsystem. |
| `SDL_INIT_HAPTIC` | `0x00001000` | Haptic (force feedback) subsystem. |
| `SDL_INIT_GAMECONTROLLER` | `0x00002000` | Game controller subsystem. |
| `SDL_INIT_EVENTS` | `0x00004000` | Events subsystem. |
| `SDL_INIT_EVERYTHING` | `0x0000F231` | All subsystems. |


### Audio Formats

| Constant | Value | Description |
|----------|-------|-------------|
| `AUDIO_U8` | `0x0008` | Unsigned 8-bit samples. |
| `AUDIO_S8` | `0x8008` | Signed 8-bit samples. |
| `AUDIO_U16LSB` | `0x0010` | Unsigned 16-bit, little-endian. |
| `AUDIO_S16LSB` | `0x8010` | Signed 16-bit, little-endian. |
| `AUDIO_U16MSB` | `0x1010` | Unsigned 16-bit, big-endian. |
| `AUDIO_S16MSB` | `0x9010` | Signed 16-bit, big-endian. |
| `AUDIO_U16` | `AUDIO_U16LSB` | System-endian unsigned 16-bit. |
| `AUDIO_S16` | `AUDIO_S16LSB` | System-endian signed 16-bit. |
| `AUDIO_S32LSB` | `0x8020` | Signed 32-bit, little-endian. |
| `AUDIO_S32MSB` | `0x9020` | Signed 32-bit, big-endian. |
| `AUDIO_S32` | `AUDIO_S32LSB` | System-endian signed 32-bit. |
| `AUDIO_F32LSB` | `0x8120` | Float 32-bit, little-endian. |
| `AUDIO_F32MSB` | `0x9120` | Float 32-bit, big-endian. |
| `AUDIO_F32` | `AUDIO_F32LSB` | System-endian float 32-bit. |
| `MIX_DEFAULT_FORMAT` | `AUDIO_S16SYS` | Default format (signed 16-bit, system endian). |


### Mixer Initialization Flags

| Constant | Value | Description |
|----------|-------|-------------|
| `MIX_INIT_FLAC` | `0x00000001` | FLAC support. |
| `MIX_INIT_MOD` | `0x00000002` | MOD (tracker) support. |
| `MIX_INIT_MP3` | `0x00000008` | MP3 support. |
| `MIX_INIT_OGG` | `0x00000010` | OGG Vorbis support. |
| `MIX_INIT_MID` | `0x00000020` | MIDI support. |
| `MIX_INIT_OPUS` | `0x00000040` | Opus support. |


### Music Type Constants (Returned by `Mix_GetMusicType`)

| Constant | Value | Description |
|----------|-------|-------------|
| `MUS_NONE` | `0` | No music loaded. |
| `MUS_CMD` | `1` | External command (rare). |
| `MUS_WAV` | `2` | WAV file. |
| `MUS_MOD` | `3` | MOD tracker file. |
| `MUS_MID` | `4` | MIDI file. |
| `MUS_OGG` | `5` | OGG Vorbis. |
| `MUS_MP3` | `6` | MP3. |
| `MUS_FLAC` | `7` | FLAC. |
| `MUS_OPUS` | `8` | Opus. |


### Fade Status Constants

| Constant | Value | Description |
|----------|-------|-------------|
| `MUS_NO_FADING` | `0` | No fade in progress. |
| `MUS_FADING_OUT` | `1` | Fading out. |
| `MUS_FADING_IN` | `2` | Fading in. |


## Exception Handling

All methods in AudioLibrary raise exceptions under the following conditions:

| Exception | When Raised |
|-----------|-------------|
| `FileNotFoundError` | The specified file does not exist. |
| `RuntimeError` | SDL2 initialization fails, mixer initialization fails, audio file loading fails, playback fails. |
| `ValueError` | Invalid AID, unsupported operation (e.g., seeking on a sound effect). |
| `ImportError` | Unable to load SDL2 libraries (Windows/macOS download failure, Linux system library missing). |

**Example:**
```python
try:
    lib.play_from_file("nonexistent.mp3")
except FileNotFoundError as e:
    print(f"File not found: {e}")
except RuntimeError as e:
    print(f"Playback error: {e}")
```

---

**End of API Reference**

This document covers all public and internal APIs for the ap_ds library version  vv3.1.2. For additional examples and usage patterns, please refer to the main README.md.


# Environment Variables

## 1. AP_DS_HIDE_SUPPORT_PROMPT

**Purpose:** Controls whether the startup banner is displayed when importing the library.

**Default:** Not set (banner displayed)

**Behavior:**
- When set to `1`, the startup message is completely suppressed.
- Useful for GUI applications, daemons, or any environment where console output should be minimized.

**Usage:**
```bash
# Linux/macOS
export AP_DS_HIDE_SUPPORT_PROMPT=1

# Windows Command Prompt
set AP_DS_HIDE_SUPPORT_PROMPT=1

# Windows PowerShell
$env:AP_DS_HIDE_SUPPORT_PROMPT=1
```

**Code Example:**
```python
import os
os.environ['AP_DS_HIDE_SUPPORT_PROMPT'] = '1'
import ap_ds  # No banner output
```


## 2. AP_DS_WAV_THRESHOLD

**Purpose:** Determines whether a WAV file is played as a sound effect (non-seekable) or as a music file (seekable, with fade support).

**Default:** `6` seconds

**Behavior:**
- Files with duration **less than** the threshold: treated as sound effects (using `Mix_PlayChannel`). **Seek operations are not supported.**
- Files with duration **greater than or equal to** the threshold: treated as music (using `Mix_PlayMusic`). Full seek and fade operations are supported.
- If the threshold is set to `30` or higher, it is automatically reset to `6` to prevent potential memory issues.
- Negative values are also reset to `6`.
- Invalid (non-numeric) values fall back to the default.

**Why This Setting Exists:**  
SDL2_mixer has two different playback mechanisms: sound effects (channels) and music. Sound effects are lightweight and suitable for short clips but cannot seek or fade. Music tracks support these advanced features but consume slightly more resources. This threshold allows automatic selection of the appropriate mechanism based on file duration.

**Usage:**
```bash
# Set threshold to 10 seconds
export AP_DS_WAV_THRESHOLD=10

# Use a very low threshold (all WAVs become sound effects)
export AP_DS_WAV_THRESHOLD=0

# Use a high threshold (only very long WAVs become music)
export AP_DS_WAV_THRESHOLD=20
```

**Validation Rules:**
```python
# Internal validation logic
if WAV_THRESHOLD >= 30:
    WAV_THRESHOLD = 6  # Prevent memory issues
elif WAV_THRESHOLD < 0:
    WAV_THRESHOLD = 6
```


## 3. AP_DS_SDL2_PATH and AP_DS_SDL2_MIXER_PATH (Linux Only)

**Purpose:** Specifies custom paths to the SDL2 and SDL2_mixer shared libraries on Linux systems. Used when system-installed libraries are not found or when users have compiled their own versions.

**Default:** Not set; libraries are searched in standard system paths (`/usr/lib`, `/usr/local/lib`, etc.) and package manager locations.

**Behavior:**
- If both variables are set and the files exist, they are loaded immediately without further search.
- After successful loading, the paths are automatically saved to `~/.config/ap_ds/sdl_paths.conf` for future runs.
- If the saved paths become invalid (files missing), the library falls back to the normal search flow.

**Usage:**
```bash
export AP_DS_SDL2_PATH=/usr/local/lib/libSDL2.so
export AP_DS_SDL2_MIXER_PATH=/usr/local/lib/libSDL2_mixer.so
```

**Configuration File:**
After the first successful manual configuration, the paths are saved to:
```
~/.config/ap_ds/sdl_paths.conf
```
File contents:
```
SDL2_PATH=/path/to/libSDL2.so
SDL2_MIXER_PATH=/path/to/libSDL2_mixer.so
```


## 4. AP_DS_SUPPRESS_WARNINGS (New in v vv3.1.2)

**Purpose:** Suppresses runtime downgrade warnings (e.g., GIL-enabled warnings).

**Default:** Not set (warnings displayed)

**Behavior:**
- When set to `1`, all runtime downgrade warnings are completely suppressed.
- Useful for production environments or scenarios where you don't want to see warning output.

**Usage:**
```bash
export AP_DS_SUPPRESS_WARNINGS=1
```


## 5. AP_DS_SHOW_CONGRATS (New in v vv3.1.2)

**Purpose:** Controls whether the congratulations message for full-performance mode is displayed.

**Default:** Not set (congratulations displayed)

**Behavior:**
- When set to `0`, hides the congratulations message for full-performance mode (GIL disabled).
- Useful for quiet mode or headless environments.

**Usage:**
```bash
export AP_DS_SHOW_CONGRATS=0
```


## 6. AP_DS_SKIP_AUTO_CHECK (New in v vv3.1.2)

**Purpose:** Controls whether the automatic runtime self-check on import is skipped.

**Default:** `0` (self-check executed)

**Behavior:**
- When set to `1`, skips the runtime self-check output on import.
- Useful for test environments or reducing startup noise.

**Usage:**
```bash
export AP_DS_SKIP_AUTO_CHECK=1
```


# v vv3.1.2 – Detailed Release Overview

## What is v vv3.1.2?

v vv3.1.2 is a major feature release of ap_ds (non-LTS), focusing on **batch parsing performance** and **Python 3.15t free-threading support**. It introduces a brand-new batch parsing API using `ProcessPoolExecutor` for true multi-core parallelism, and fully adapts to Python 3.15t's GIL-less environment.

**Version Type:** Feature release (non-LTS)  
**Support Period:** Approximately 6 months  
**Target Users:** Developers needing batch parsing, early adopters wanting to experience Python 3.15t features  
**Production Recommendation:** Continue using v3.0.0 LTS, wait for v4.0.0 LTS


## Major New Features

### 1. Batch Parsing API

| API | Description |
|-----|-------------|
| `batch_get_metadata()` | Batch parse audio files, returns full metadata list |
| `batch_get_duration()` | Batch get audio durations, returns `{path: duration}` |
| `batch_get_metadata_by_type()` | Filter batch parsing by format (e.g., parse only MP3) |

These APIs use `ProcessPoolExecutor` for process-level parallelism, achieving true multi-core parallel acceleration in Python 3.15t (GIL-less) environments.


### 2. Performance Improvements

**120 MP3 File Test Results:**

| Method | Time | Speedup |
|--------|------|---------|
| Serial parsing | 1.367s | 1.00x |
| 8-process parallel | **0.331s** | **4.13x** |

**Compared to Mutagen:**

| Library | Method | 120 Files Time |
|---------|--------|----------------|
| Mutagen | Single-threaded | 0.973s |
| **ap_ds v vv3.1.2** | **8-process parallel** | **0.331s** (2.94x faster) |


### 3. Python 3.15t Free-Threading Support

- **True GIL-less parallelism:** Batch parsing scales linearly on multi-core CPUs
- **Runtime self-check:** Automatically detects GIL status on import and provides clear prompts
- **Full-performance users** see `🎉 ap_ds: GIL disabled (free-threading mode)`
- **Non-full-performance users** see `⚠️ ap_ds: GIL is enabled (multi-core parallelism limited)`


### 4. DAP Deduplication Optimization

`_add_to_dap_recordings()` upgraded from O(n) linear scan to **O(1) set-based deduplication**, delivering significant performance gains for large playlists. O(n) fallback mechanism is retained to ensure stability in edge cases.


### 5. Startup Acceleration: Lazy Import

Python 3.15+ users automatically benefit from `lazy import` – heavy modules (ctypes, urllib.request, struct, json, etc.) are loaded on demand, significantly speeding up `import ap_ds`.


### 6. New Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `AP_DS_SUPPRESS_WARNINGS` | Not set | Set to `1` to suppress downgrade warnings |
| `AP_DS_SHOW_CONGRATS` | Not set | Set to `0` to hide full-performance congratulations |
| `AP_DS_SKIP_AUTO_CHECK` | `0` | Set to `1` to skip import-time self-check |


### 7. Runtime Diagnostics

Two new standalone utility functions, usable without instantiation:

```python
from ap_ds import is_full_performance, get_runtime_info

if is_full_performance():
    print("🚀 Full-performance mode!")

info = get_runtime_info()
print(info["python_version"], info["gil_enabled"])
```


## Version Relationship

| Version | Type | Support Period | Use Case |
|---------|------|----------------|----------|
| **v3.0.0 LTS** | Long-Term Support | Until March 2031 | Production environments |
| **v vv3.1.2** | Feature Release | ~6 months | Early adopters / batch parsing needs |
| **v4.0.0 LTS** (planned) | Long-Term Support | TBD | Based on Python 3.15 stable release |


## Upgrade Recommendations

| User Type | Recommendation |
|-----------|----------------|
| Production environment | Continue using **v3.0.0 LTS**, wait for v4.0.0 LTS |
| Development/Testing | Upgrade to **v vv3.1.2** to experience new features |
| Need batch parsing | **Must upgrade** to v vv3.1.2 |
| Python 3.15t users | **Must upgrade** to v vv3.1.2 to leverage GIL-less advantages |

```bash
pip install --upgrade ap_ds
```


# Technical Architecture

## Core Components

```
ap_ds/
├── __init__.py          # Package entry point, version import, banner display
├── player.py            # Main AudioLibrary class, SDL2 bindings, playback logic
├── audio_parser.py      # Metadata parser factory, unified API
├── audio_info.py        # Format-specific parsers (WAV, FLAC, MP3, OGG, AAC)
└── _version.py          # Auto-generated version file (created at build time)
```


### 1. Player Module (`player.py`)

**Purpose:** The core of the library. Manages SDL2 initialization, audio playback, caching, and DAP recording.

**Key Classes & Functions:**

- `AudioLibrary`: The main class for user interaction
  - **Initialization:** `__init__(frequency, format, channels, chunksize)` – Sets up SDL audio and mixer
  - **Playback:** `play_from_file()`, `play_from_memory()`, `new_aid()`
  - **Control:** `pause_audio()`, `play_audio()`, `stop_audio()`, `seek_audio()`
  - **Volume:** `set_volume()`, `get_volume()`
  - **Fade:** `fadein_music()`, `fadein_music_pos()`, `fadeout_music()`, `is_music_playing()`, `is_music_paused()`, `get_music_fading()`
  - **Metadata:** `get_audio_duration()`, `get_audio_metadata()`
  - **Batch Parsing (v vv3.1.2):** `batch_get_metadata()`, `batch_get_duration()`, `batch_get_metadata_by_type()`
  - **DAP:** `save_dap_to_json()`, `get_dap_recordings()`, `clear_dap_recordings()`

**Internal Data Structures:**
- `self._audio_cache`: `dict[str, Mix_Chunk]` – Cached sound effects (short files)
- `self._music_cache`: `dict[str, Mix_Music]` – Cached music tracks
- `self._channel_info`: `dict[int, dict]` – Active playback sessions, keyed by SDL channel ID
- `self._aid_to_filepath`: `dict[int, str]` – AID (Audio ID) to file path mapping
- `self._dap_recordings`: `list[dict]` – DAP history records
- `self._dap_records_set`: `set[str]` – O(1) deduplication set (v vv3.1.2)

**Platform Abstraction:**
- `import_sdl2()`: Intelligently loads SDL2 libraries on Windows, macOS, and Linux
- `download_sdl_libraries()`: Downloads platform-specific binaries with hash verification
- `check_sdl_libraries_exist()`: Verifies required files are present
- `load_sdl2_from_directory()`: Loads libraries from package directory or system paths

**SDL2 Bindings:**
- All SDL2 and SDL2_mixer functions are bound via `ctypes`
- Function prototypes (`argtypes`, `restype`) are set for all platforms to prevent segfaults
- Binding code is executed unconditionally to ensure cross-platform stability


### 2. Metadata Parser Module (`audio_parser.py`)

**Purpose:** Provides a unified interface for audio metadata extraction. Handles format detection and dispatches to the appropriate parser.

**Key Functions:**
- `get_audio_parser()`: Singleton factory returning an `AudioParser` instance
- `AudioParser.get_audio_metadata(file_path)`: Returns a dictionary containing `path`, `format`, `duration`, `length`, `sample_rate`, `channels`, `bitrate`
- `AudioParser.batch_get_metadata()`: Batch parsing (v vv3.1.2)

**Fallback Behavior:** If a format-specific parser fails, returns `None`. No exceptions are raised during metadata parsing; errors are logged.


### 3. Format-Specific Parsers (`audio_info.py`)

**Purpose:** Low-level parsers for each supported format. Written in pure Python with no external library dependencies.

| Format | Parser Class | Accuracy | Method |
|--------|--------------|----------|--------|
| WAV | `WAVFile` | 100% | Reads RIFF chunks, extracts sample rate, channels, block align, and data size. Calculates exact duration from total frames. |
| FLAC | `FLACFile` | 100% | Reads the STREAMINFO block (always present). Extracts sample rate, channels, and total samples from metadata. |
| MP3 | `MP3File` | >98% | Frame-by-frame scan using MP3 frame headers. Accumulates sample count and estimates duration based on sample rate. |
| OGG | `OGGFile` | 99.99% | Reads Ogg pages, extracts granular position (total samples) from the last page. Requires scanning the entire file but is highly accurate. |
| AAC (ADTS) | `AACFile` | >99% | Frame-by-frame ADTS header parsing, accumulating sample count. Accurate for files with consistent frame sizes. |


### 4. Batch Parsing Architecture (New in v vv3.1.2)

**Core Mechanism:** Uses `ProcessPoolExecutor` for process-level parallelism. Each subprocess independently parses files, avoiding file handle contention.

**Advantages:**
- Fully stable on Windows with free-threading builds
- True multi-core parallelism, not limited by GIL
- Process isolation eliminates resource contention

**Workflow:**
1. User calls `batch_get_metadata()`
2. If a directory is provided, recursively scans to collect all supported audio files
3. Creates a `ProcessPoolExecutor` process pool
4. Each file is submitted to a worker process
5. Each worker process calls `_parse_single_file()` to parse a single file
6. Collects and returns all successfully parsed metadata


### 5. DAP (Dvs Audio Playlist) System

**Purpose:** Automatically records playback history, storing only metadata so applications can build listening histories without storing audio data.

**Workflow:**
1. User calls `play_from_file()` or `play_from_memory()`
2. Method calls `_add_to_dap_recordings(file_path)`
3. `_add_to_dap_recordings()` retrieves metadata via `get_audio_metadata_by_path()`
4. Creates a record containing `path`, `duration`, `bitrate`, and `channels`
5. Adds to `_dap_recordings` after checking for duplicates using **O(1) set-based deduplication** (v vv3.1.2)
6. User can call `save_dap_to_json()` at any time to persist the list to an `.ap_ds-dap` JSON file

**Memory Characteristics:**
- Per record: ~150 bytes (path string + three integers)
- 10,000 records: ~1.5 MB RAM, ~2-3 MB JSON file
- No audio data is stored


### 6. SDL2 Integration & ctypes Bindings

**Why ctypes?**  
ctypes is part of the Python standard library, so it adds no external dependencies. It allows direct calling of C functions from shared libraries (DLLs, dylibs, .so files). This is the most lightweight way to interface with SDL2.

**Function Prototypes:**  
`argtypes` and `restype` are set for every SDL2 and SDL2_mixer function used. This is critical for cross-platform stability:
- Without `argtypes`, Python may pass incorrect arguments, leading to segfaults
- Without `restype`, Python may misinterpret return values (especially for pointer types)


### 7. Error Handling Philosophy

ap_ds follows a **fail-fast, fail-clearly** philosophy:

- **File not found:** `FileNotFoundError` with the path included
- **Unsupported format:** `RuntimeError` with a descriptive message
- **SDL2 initialization failure:** `RuntimeError` with the SDL error string
- **Download failure:** `Exception` with failure details (hash mismatch, network error, etc.)

**Why not structured error codes?**  
Structured error codes (like `-1` for failure, `0` for success) are common in C APIs but are not Pythonic in Python. Python exceptions are preferred because they force callers to explicitly handle errors and provide rich context.


# Frequently Asked Questions

### 1. Which version should I use for production?

**v3.0.0 LTS** is the recommended version for all production deployments. It receives five years of security updates and critical bug fixes. No breaking changes will be introduced during the LTS period.

**v vv3.1.2** is a feature release (non-LTS) suitable for users who need batch parsing and Python 3.15t support.


### 2. Can I use ap_ds in commercial products?

Yes, absolutely. The license explicitly allows commercial use, including integration into commercial products, cloud services, and SaaS platforms, completely free of charge. You must comply with the attribution requirements.


### 3. Why is the library so small?

ap_ds focuses on playing and parsing the four most common formats, avoiding bloat from editing/transcoding features. It builds on the efficient SDL2 C library and uses only the Python standard library. Total size is 2.5MB on Windows and 3.36MB on macOS.


### 4. How accurate is MP3 duration parsing?

MP3 duration parsing is >98% accurate. This is due to the format's variable header complexity. WAV and FLAC are guaranteed 100% accurate, while OGG is 99.99%.


### 5. Does it work on embedded devices?

Yes! v3.0.0 LTS has been tested on Orange Pi 4 Pro and Raspberry Pi 5 (ARM64) running Ubuntu 22.04. Memory growth is minimal (~4MB after extensive testing). Audio output via 3.5mm works without modification.


### 6. What are the system requirements?

- **Windows:** Windows 7+, Python 3.7+
- **macOS:** macOS 10.9+, Python 3.7+
- **Linux:** Modern distributions, Python 3.7+, SDL2 libraries (auto-installed via package manager where possible)
- **Embedded:** ARM64 devices running Ubuntu 22.04 or similar


### 7. How do I suppress the startup banner?

Set the environment variable `AP_DS_HIDE_SUPPORT_PROMPT=1` before importing the library:

```python
import os
os.environ['AP_DS_HIDE_SUPPORT_PROMPT'] = '1'
import ap_ds
```


### 8. How do I configure the WAV threshold?

Set `AP_DS_WAV_THRESHOLD` to the desired number of seconds. Files shorter than this threshold will be treated as sound effects (no seek support), while files equal to or longer than the threshold will be treated as music (full seek and fade support).


### 9. Why does v vv3.1.2 use multi-processing instead of multi-threading?

In Python 3.15t (GIL-less) environments, multi-threaded file I/O on Windows suffers from file handle contention issues, leading to `BrokenProcessPool` errors. Multi-processing (`ProcessPoolExecutor`) completely avoids this through process isolation, achieving true parallelism with 100% stability.


### 10. How many processes should I use for batch parsing?

We recommend setting `max_workers` to the number of CPU cores or slightly lower. For most systems, **4-8 processes** is optimal. With fewer files, process startup overhead may outweigh parallelism benefits; with >100 files, the parallelism advantage is significant.


### 11. What is Free-Threading Python?

Free-Threading Python (filename with `t`) is a Python build that removes the GIL (Global Interpreter Lock). This means multiple threads can execute Python bytecode simultaneously, enabling true multi-core parallelism. Both Python 3.14t and 3.15t support this feature.


### 12. How do I verify I'm using the free-threading version?

Run the following command:

```bash
python3.14t -c "import sys; print(sys._is_gil_enabled())"
```

A `False` output means GIL is disabled (free-threading version).


### 13. How does v vv3.1.2 compare to Mutagen?

| Comparison | Mutagen | ap_ds v vv3.1.2 |
|------------|---------|--------------|
| Batch parsing | ❌ No | ✅ Yes |
| Multi-core parallelism | ❌ Single-threaded | ✅ 8-process parallel |
| 120 files time | 0.973s | **0.331s** |
| Python 3.15t support | ❌ No | ✅ Full support |

**ap_ds v vv3.1.2 is 2.94x faster than Mutagen.**


### 14. Will v3.0.0 LTS receive v vv3.1.2's new features?

No. LTS releases receive only security updates and critical bug fixes, **no new features**. This is to ensure API stability and predictability for LTS users. New features will only be available in feature releases (v vv3.1.2, v3.2.0, etc.).


### 15. When is the next LTS release?

**v4.0.0 LTS** is planned for release **after Python 3.15's stable release** (expected October 2026). Once Python 3.15 is stable and all free-threading-related issues are resolved, ap_ds will release a new LTS version based on it.


### 16. How do I get technical support?

Free support is available through:

- **GitCode Issues (recommended for global users)**: [https://gitcode.com/dvsxt/ap_ds/issues](https://gitcode.com/dvsxt/ap_ds/issues)
- **Gitee Issues (China mirror)**: [https://gitee.com/dssxt/ap_ds/issues](https://gitee.com/dssxt/ap_ds/issues)
- **Official Homepage**: [https://apds.top](https://apds.top)
- **Email**: me@dvsyun.top or dvs6666@163.com

Response times: Within 7 business days for standard inquiries, 48 hours for critical issues.


## Version History

### v vv3.1.2 (July 2026) – Performance & Batch Parsing Edition

**This is a major feature release of ap_ds** (non-LTS), focusing on **batch parsing performance** and **Python 3.15t free-threading support**.

#### 🚀 New Features

**1. Batch Parsing API (Brand New)**

| API | Description |
|-----|-------------|
| `batch_get_metadata()` | Batch parse audio files, returns full metadata list |
| `batch_get_duration()` | Batch get audio durations, returns `{path: duration}` |
| `batch_get_metadata_by_type()` | Filter batch parsing by format (e.g., parse only MP3) |

**2. Python 3.15t Free-Threading Support**

- Fully adapted for Python 3.15t (GIL-less) environments
- Runtime self-check: Automatically detects GIL status on import
- Full-performance users see `🎉 ap_ds: GIL disabled (free-threading mode)`
- Non-full-performance users see `⚠️ ap_ds: GIL is enabled (multi-core parallelism limited)`

**3. DAP Deduplication Optimization**

`_add_to_dap_recordings()` upgraded from O(n) linear scan to **O(1) set-based deduplication**, delivering significant performance gains for large playlists. O(n) fallback mechanism is retained to ensure stability in edge cases.

**4. Startup Acceleration: Lazy Import**

Python 3.15+ users automatically benefit from `lazy import` – heavy modules (ctypes, urllib.request, struct, json, etc.) are loaded on demand, significantly speeding up `import ap_ds`.

**5. Runtime Diagnostic Functions**

Two new standalone utility functions, usable without instantiation:
- `is_full_performance()` – Checks if running in full-performance mode (3.15t + profiling)
- `get_runtime_info()` – Returns diagnostic info: Python version, GIL status, profiling status, etc.

**6. Runtime Self-Check**

Automatically executes a runtime self-check on import, printing environment information (Python version, GIL status, profiling availability, performance mode, CPU core count, etc.). Can be skipped with `AP_DS_SKIP_AUTO_CHECK=1`.

**7. New Environment Variables**

| Variable | Default | Description |
|----------|---------|-------------|
| `AP_DS_SUPPRESS_WARNINGS` | Not set | Set to `1` to suppress downgrade warnings |
| `AP_DS_SHOW_CONGRATS` | Not set | Set to `0` to hide full-performance congratulations |
| `AP_DS_SKIP_AUTO_CHECK` | `0` | Set to `1` to skip import-time self-check |


#### ⚡ Performance Improvements

**120 MP3 File Test Results:**

| Method | Time | Speedup |
|--------|------|---------|
| Serial parsing | 1.367s | 1.00x |
| 8-process parallel | **0.331s** | **4.13x** |

**Compared to Mutagen:**

| Library | Method | 120 Files Time |
|---------|--------|----------------|
| Mutagen | Single-threaded | 0.973s |
| **ap_ds v vv3.1.2** | **8-process parallel** | **0.331s** (2.94x faster) |

**Compared to v3.0.0:**

| Test | v3.0.0 (with GIL) | v vv3.1.2 (without GIL) | Conclusion |
|------|-------------------|----------------------|------------|
| 8-way 120 files | 1.285s (0.66x) ❌ | **0.331s (4.13x)** | 🚀  vv3.1.2 is 3.88x faster |
| Best time | 0.789s (2 threads) | **0.331s (8 processes)** | 🚀 ** vv3.1.2 is 2.38x faster** |


#### 🔧 Changes

**Underlying Improvements:**
- Batch parsing uses `ProcessPoolExecutor` instead of `ThreadPoolExecutor`
- Achieves true multi-core parallelism in Python 3.15t (GIL-less) environments
- Completely removed `profiling`-related code (avoiding performance overhead)

**New APIs:**
- `AudioLibrary.batch_get_metadata()`
- `AudioLibrary.batch_get_duration()`
- `AudioLibrary.batch_get_metadata_by_type()`
- `AudioLibrary.is_full_performance()` → changed to top-level function
- `AudioLibrary.get_runtime_info()` → changed to top-level function

**API Changes:**
- No breaking changes – fully backward compatible with v3.0.0

**Documentation Updates:**
- Added complete batch parsing API documentation
- Added environment variable documentation
- Added performance comparison data (vs Mutagen, vs v3.0.0)
- Updated apds.top as the official primary repository

**Dependency Changes:**
- No new external dependencies
- Maintains zero Python external dependencies


#### 🐛 Fixed Issues

- Fixed `ThreadPoolExecutor` file handle contention causing crashes on Windows + Python 3.15t
- Fixed `ProcessPoolExecutor` subprocess re-executing module-level code causing crashes (protected with `if __name__ == "__main__"`)


#### 📦 Version Relationship

| Version | Type | Support Period |
|---------|------|----------------|
| **v3.0.0 LTS** | Long-Term Support | Until March 2031 |
| **v vv3.1.2** | Feature Release | ~6 months |


#### 🔗 Repository Changes

- **Official Primary Repository:** [https://apds.top](https://apds.top) (Permanent official home)
- **Primary Mirror:** [GitCode](https://gitcode.com/dvsxt/ap_ds)
- **China Mirror:** [Gitee](https://gitee.com/dssxt/ap_ds)
- **Deprecated:** GitHub (dvs-web/ap_ds), GitLab (JiHu)


#### ⬆️ Upgrade Command

```bash
pip install --upgrade ap_ds
```

> ⚠️ **Note:** v vv3.1.2 is a feature release (non-LTS). For production environments, please continue using v3.0.0 LTS.


---
### v3.0.0 LTS (March 22, 2026) – First Long-Term Support Release

**This is ap_ds's first LTS release.** After years of refinement, extensive real-world testing, and a thorough internal resource management refactor, this release is production-ready for mission-critical applications, enterprise deployments, and personal projects.

**New Features:**
- **Deterministic resource cleanup** – Replaced unreliable `__del__` finalizers with explicit exit-time handlers
- **Hash-verified downloads** – Every downloaded SDL2 library is validated against hardcoded SHA-256 hashes before use
- **Full test coverage** – Tested across all platforms with zero memory leaks
- **5-year support period** – Until March 22, 2031, with free technical support

**No breaking changes** – Fully backward compatible with v2.x.

```bash
pip install --upgrade ap_ds
```


### v2.4.2 (March 22, 2026) – Development Mistake

**This version was accidentally uploaded with a development-stage `player.py` file.** While it technically works, it may contain subtle issues and **is not recommended for use in any real project.**

> ⚠️ This version was a development mistake and is intended only for curiosity – please do not use it in production.


### v2.4.1 (March 1, 2026) – Documentation Update

Updated PyPI documentation to fully reflect v2.4.0's new features, including detailed API descriptions, usage examples, and environment variable documentation.

**Changes:**
- Updated PyPI project description
- Added detailed examples for all new fade functions
- Documented `AP_DS_HIDE_SUPPORT_PROMPT` environment variable
- Improved quick-start guide

> **Note:** This release contains no code changes – only documentation improvements.


### v2.4.0 (March 1, 2026) – Audio Effects & Engineering Improvements

**Introducing professional audio transitions and important internal engineering upgrades.**

**🎵 New Audio Control Functions:**

| Function | Description |
|----------|-------------|
| `fadein_music(aid, loops=-1, ms=0)` | Fades in music over the specified milliseconds |
| `fadein_music_pos(aid, loops=-1, ms=0, position=0.0)` | Fades in music from a specified position |
| `fadeout_music(ms=0)` | Fades out currently playing music |
| `is_music_playing()` | Checks if music is currently playing |
| `is_music_paused()` | Checks if music is paused |
| `get_music_fading()` | Gets the current fade status |

**🧠 Engineering Improvements:**
- Cleaner startup banner, controllable via `AP_DS_HIDE_SUPPORT_PROMPT`
- Centralized version management
- Robust import system (dual-layer fallback)
- Unified project URLs

**No breaking changes** – All existing code continues to work.


### v2.3.6 (February 27, 2026) – Documentation Update

Updated PyPI documentation with detailed license information and version history, added more examples.


### v2.3.5 (February 26, 2026) – Stability Optimization & Embedded Validation

**Six-dimensional test coverage:**
1. Library loading & initialization
2. Playback testing (MP3, FLAC, OGG, WAV)
3. Seek testing
4. Memory pressure & leak detection (~4MB growth)
5. Metadata parsing accuracy
6. DAP system validation

**Embedded Platform Support:**
- Orange Pi 4 Pro (Allwinner A733)
- Raspberry Pi 5 (BCM2712)

**Bug Fixes:**
- Fixed WAV files being incorrectly treated as sound effects – configurable via `AP_DS_WAV_THRESHOLD`


### v2.3.4 (February 10, 2026) – Linux Smart Import System

**Revolutionary Linux support improvements with four-layer fallback strategy:**

1. System library check
2. User configuration check
3. Automatic package manager installation (apt-get, dnf, pacman)
4. Interactive guidance

**Automatic Configuration Saving:**
- Environment variables (`AP_DS_SDL2_PATH`, `AP_DS_SDL2_MIXER_PATH`)
- Persistent configuration file (`~/.config/ap_ds/sdl_paths.conf`)


### v2.3.3 (February 9, 2026) – Critical Bug Fix & Platform Stabilization

**🚨 Critical Update:** Fixed a severe segfault that caused the library to fail on macOS and Linux.

**Root Cause:** Earlier versions only defined C function prototypes (ctypes argtypes/restype) on Windows, leading to memory access violations on other operating systems.

**Solution:** All necessary C function bindings are now defined unconditionally after loading the SDL2 libraries.


### v2.3.2 (February 9, 2026) – Linux Support Enhancement

**Expanded Linux support with interactive setup.**

**Interactive Linux Support:**
1. Use system-installed libraries
2. Specify compiled .so file path
3. Get detailed compilation instructions


### v2.3.1 (February 9, 2026) – Documentation Update

Improved README.md with better examples and explanations. Fixed minor errors in documentation examples.


### v2.3.0 (January 31, 2026) – DAP Recording System

**Introducing the DAP (Dvs Audio Playlist) system.**

**Core Features:**
- **Intelligent auto-recording:** Automatically triggered in `play_from_file()`, `play_from_memory()`
- **Lightweight design:** Metadata only, no audio data
- **Standardized file format:** `.ap_ds-dap` extension, JSON format
- **Intelligent deduplication:** Automatically avoids duplicate records for the same file

**New APIs:**
- `_add_to_dap_recordings(file_path)` – Internal use
- `save_dap_to_json(save_path)` – Save as JSON
- `get_dap_recordings()` – Get all records
- `clear_dap_recordings()` – Clear records


### v2.2.0 (January 19, 2026) – Cross-Platform Revolution

**From single-platform to cross-platform.**

**Major New Features:**

**1. Full macOS Support**
- Automatic download and installation of SDL2.framework, SDL2_mixer.framework
- Intelligent .dmg file extraction and framework loading
- Maintains extreme lightness: only 3.36MB (vs Windows 2.5MB)

**2. Enhanced Automatic Dependency Management**
- Cross-platform intelligent download strategy
- Full error handling and retry mechanism
- Local caching of dependency files


### v2.1.4 (January 18, 2026) – Stable Release

**Production-ready stable version.**

- Core stability: Extensively tested, no known critical bugs
- Extremely lightweight: Only 2.5MB complete solution
- Full documentation: Detailed technical manual and examples


### v2.1.0 (December 26, 2025) – Feature Enhancement

**Professional feature expansion.**

**New Features:**
- Metadata enhancement: More precise audio information parsing
- Playback accuracy improvements: Better time control and seeking


### v2.0.0 (November 5, 2025) – Architecture Refactor

**Introducing the modern audio management system.**

**Major Improvements:**
- **AID System:** Unified audio instance management
- **Architecture Refactor:** Modular design for improved maintainability
- **Smart Memory Management:** Automatic cleanup of unused audio resources
- **State Management:** Unified playback state tracking


### v1.0.0 (July 8, 2025) – Initial Release

**Project birth, foundational functionality.**

**Core Features:**
- Basic audio playback: MP3, WAV, FLAC, OGG formats
- Playback controls: Play, pause, stop, seek basic API
- Volume control: Real-time volume adjustment (0-100%)
- Lightweight design: ~2MB initial release


---

## License

This project is licensed under the **DVS Audio Library (ap_ds) Open Source License Version 2.0**. The full license text follows. By using, copying, modifying, or distributing this software, you accept all terms and conditions of this license.

---

---

# DVS Audio Library (ap_ds) Open Source License Version 2.0

**Version: 2.0**
**Effective Date: March 22, 2026**
**Applies to: ap_ds version 2.4.1 and above (except for subsequent license updates)**
**Project Homepage: https://www.dvsyun.top/ap_ds | https://apds.top**

---

## 1. Definitions

1.1. **"Software"** means the DVS Audio Library (ap_ds) project and all its components, source code, object code, and related documentation. The official name of this project is "ap_ds", and the following names are also granted as officially recognized brand identifiers:
   - AP_DS
   - Audio Library By DVS
   - DVS Audio Player
   (All of the above names are case-insensitive and are considered officially recognized brand names.)

1.2. **"Source Code"** means the human-readable form of the Software, which is the basis for modification, study, and distribution.

1.3. **"Modified Version"** means any derivative work created by modifying, supplementing, translating, or otherwise altering the Software, in whole or in part.

1.4. **"Distribute"** means making the Software or a Modified Version available to any third party by any means or medium.

1.5. **"You"** means any individual or legal entity exercising the rights granted under this License.

1.6. **"Independent Brand"** means a completely new project name, logo, and brand identity that has no confusing association with the official names of the Software (including but not limited to "ap_ds", "AP_DS", "Audio Library By DVS", "DVS Audio Player", and any variants thereof).

---

## 2. Grant of License

Subject to the terms and conditions of this License, the Author hereby grants You a perpetual, worldwide, royalty-free, non-exclusive, irrevocable right to:

2.1. **Use and Run**: Run the Software on any computer system for any lawful purpose.

2.2. **Copy and Distribute**: Make any number of copies of the Software and Distribute them.

2.3. **Study and Modify**: Study the Software's Source Code and make any modifications to meet Your needs.

2.4. **Integrate and Commercially Use**: Integrate the Software into Your products or projects, and use it in any commercial context, including but not limited to commercial product integration, cloud service deployment, selling solutions incorporating the Software, and internal corporate use.

---

## 3. Obligations and Restrictions

### 3.1. Attribution and Source Identification
Any time the Software or a Modified Version is used, Distributed, or integrated, You must:

   a) **Retain Original Copyright Notices**: Keep intact all original copyright, patent, and trademark notices in all copies of the Software.

   b) **Provide Prominent Source Attribution**: Clearly and conspicuously state the following information in the software documentation, official website, user interface, or related materials:
      ```
      Based on DVS Audio Library (ap_ds) v[version number]
      Original Author: Dvs (DvsXT)
      Project Homepage: https://www.dvsyun.top/ap_ds | https://apds.top
      ```

   c) **Add Notice for Modified Versions**: If You Distribute a Modified Version, in addition to the attribution above, You must add the following notice:
      ```
      This is a modified version maintained by [Your Name/Organization].
      Support: [Your Contact Information].
      This version is not the official version and is not affiliated with the original author.
      ```

### 3.2. Brand Protection
To prevent brand confusion and project fragmentation, Modified Versions must comply with the following strict rules:

   a) **Prohibition on Using Original Brand Names**: You must not name a Modified Version "ap_ds", "AP_DS", "Audio Library By DVS", "DVS Audio Player", or any variant, combination, or derivative that could cause confusion.

   b) **Requirement for Independent Brand**: Modified Versions must use a completely independent project name and establish their own independent project identity, documentation, and community.

   c) **Maintainer Responsibility Statement**: The distributor of a Modified Version must state prominently on their project homepage or in a conspicuous location:
      ```
      This project is based on DVS Audio Library (ap_ds) but has evolved independently and is fully maintained by [Your Name].
      For the original version, please visit: https://www.dvsyun.top/ap_ds or https://apds.top.
      The maintainer is solely responsible for any issues related to this project.
      ```

### 3.3. Quality Commitment for Modified Versions
If You Distribute a Modified Version, You must:

   a) **Clearly State the Nature of Modifications**: Clearly indicate that this is a modified version and list the key modifications and compatibility notes compared to the original version.

   b) **Provide Technical Support**: Provide a valid means of technical support contact for the Modified Version You distribute, and define the scope of support.

   c) **Not Mislead Users**: You must not imply in any way that Your Modified Version is officially endorsed, supported, or is a continuation of the original project.

### 3.4. Prohibited Uses
You must not use the Software for any illegal activities, malicious purposes, or actions that violate local laws or regulations, including but not limited to:
   a) Disrupting computer systems or network security.
   b) Distributing malware or viruses.
   c) Infringing on the intellectual property or privacy rights of others.

---

## 4. Patent Grant

4.1. **Patent License**: The Author hereby grants You a worldwide, royalty-free, non-exclusive, non-transferable patent license to make, use, sell, offer for sale, import, or otherwise transfer the Software.

4.2. **Patent Defense Termination**: If You or Your affiliates file a patent infringement lawsuit against the Author regarding the Software, all rights granted to You under this License will automatically and immediately terminate.

---

## 5. Technical Transparency and Security

5.1. **Right to Security Review**: Any user has the right to conduct a security audit of the Software's Source Code. Commercial users may engage third-party professionals for this purpose.

5.2. **Security Reporting**: Reporting discovered security issues to the original Author (me@dvsyun.top) is encouraged, and public disclosure after resolution is supported.

5.3. **No Backdoors Commitment**: The officially released version commits to containing no malicious code, backdoors, or user-data collection features without explicit user consent.

---

## 6. Disclaimer of Warranty and Limitation of Liability

6.1. **Disclaimer of Warranty**: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND ABSENCE OF ERRORS.

6.2. **Limitation of Liability**: TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES (INCLUDING BUT NOT LIMITED TO LOSS OF PROFITS, DATA LOSS, OR BUSINESS INTERRUPTION) ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE.

---

## 7. License Management and Termination

7.1. **Version Control**: This License is version 2.0. Subsequent versions will be published on the project homepage. You may choose to follow the terms of this version or any later version.

7.2. **Compatibility**: This License is compatible with the MIT, BSD 3-Clause, and Apache 2.0 licenses.

7.3. **Automatic Termination**: Your rights under this License will terminate automatically if You fail to comply with its terms. However, if You cease all non-compliance and cure all violations within 30 days of receiving notice from the copyright holder, and the copyright holder has not terminated Your rights within that period, Your rights will be reinstated.

---

## 8. Governing Law and Dispute Resolution

8.1. **Governing Law**: This License shall be governed by the laws of the People's Republic of China, without regard to its conflict of law provisions.

8.2. **Dispute Resolution**: Any dispute arising out of or in connection with this License shall first be resolved through friendly negotiation. If negotiation fails, either party may submit the dispute to the competent people's court located in the project author's domicile.

---

## 9. Contact Information

9.1. **Licensing and Inquiries**:
   - Email: me@dvsyun.top or dvs6666@163.com
   - Project Homepage: https://www.dvsyun.top/ap_ds | https://apds.top
   - Response Time: Within 7 business days

9.2. **Technical Support**:
   - Priority should be given to submitting issues via GitCode Issues.
   - Urgent matters can be directed to the emails above.

---

**BY USING, COPYING, MODIFYING, OR DISTRIBUTING THE SOFTWARE, YOU ACCEPT ALL TERMS AND CONDITIONS OF THIS LICENSE.**

---

---

## Final Note

ap_ds is built on a simple philosophy: **focus on playback and parsing, stay lightweight, and let developers build great applications.**

We welcome feedback, bug reports, and contributions. If you have questions or concerns, please contact us through the official channels.

**Thank you for using ap_ds!**
