Files
ap_ds/README.md
T
2026-08-27 19:21:49 +08:00

161 KiB
Raw Blame History

ap_ds: A Lightweight Python Audio Library

Current Version: v4.0.1 – Test Package Fix

Release Date: August 11, 2026


⚠️ Important Notice: Test Suite Missing from v4.0.0 on PyPI (Upgrade Optional)

TL;DR

If you only use ap_ds for playback and metadata parsing — v4.0.0 is completely fine. No upgrade needed.

If you want to run the CI/CD test suite after installation — please upgrade to v4.0.1 (Test directory included).

What Happened?

When publishing v4.0.0 to PyPI, we discovered a packaging misconfiguration: the ap_ds/Test/ directory was not included in the published wheel and source archives.

In other words, users who installed via pip install ap-ds==4.0.0 will not see the Test/ folder under site-packages/ap_ds/, which means the three test scripts — CI,CD_TEST.py, GUI_TEST.py, and IMPORT_TEST.py — are unavailable after installation.

Why Did This Happen?

The culprit is find_packages() in setup.py.

find_packages() is setuptools' automatic package discovery mechanism, and its rule is simple: only directories containing an __init__.py file are recognized as Python packages. Our ap_ds/Test/ directory only contained the three test scripts — no __init__.py — so setuptools silently skipped it during the build. It wasn't deliberately excluded; it was simply never seen.

Think of it like a courier who only grabs what's on the table while the user manual sits forgotten in a drawer — nothing was lost, it just never made it into the box.

How Big Is the Impact?

Very small. Specifically:

  • ✅ Core functionality is completely unaffected: AudioLibrary playback, get_audio_duration(), get_audio_metadata(), batch_get_metadata(), and all other APIs work exactly as intended
  • ✅ All 418 tests pass: We ran the full CI/CD test suite against the source tree — 418 PASS / 0 FAIL (including 8 real listening tests)
  • ❌ The only impact: after installing the PyPI release, you don't have the test scripts locally to run — that's it

Already Fixed

We have added an __init__.py to ap_ds/Test/, turning it into a proper sub-package. v4.0.1 now includes the complete Test directory, so pip install ap-ds==4.0.1 gives you a runnable test suite right away.

Upgrade Recommendation

Your Situation Recommendation
Only use the library for playback/parsing Don't upgrade — v4.0.0 is stable
Need to run tests or contribute code Upgrade to v4.0.1
Want the latest fixes Upgrade for the complete package

Whether you upgrade or not, the core functionality is identical. This notice is simply to keep users informed, so nobody mistakes the missing Test directory for a code bug.

— The DVS Development Team


⚠️ Important Notice: v3.1.x Bugs and the Road to 4.0.0

The 3.1.x Issue – Yes, We Broke It. Again.

Let's not sugarcoat this: v3.1.2 is still broken. The get_audio_duration() and get_audio_metadata() functions fail to return correct durations in certain environments. Sometimes they return 0. Sometimes they return None. Sometimes they return a value that's completely wrong. The worst part? It's inconsistent – the bug only appears under specific import orders, specific Python versions, and seemingly random combinations of file formats. Trying to reproduce it was like trying to catch a ghost.

Honestly, we're not entirely sure ourselves. The codebase had become such an unholy spaghetti nightmare that tracing the execution path required a flowchart, a debugger, and multiple cups of coffee. There were circular imports. There were redundant wrapper functions. There was a audio_info.py that existed only to call functions from audio_parser.py which then called back to audio_info.py. It was a house of cards held together by hope and bad decisions.

The Self‑Deprecating Truth – We Rewrote Everything

After spending hours trying to patch the mess, we came to a painful realization: this code is beyond repair. It wasn't just one bug – it was a structural problem caused by years of incremental changes, quick fixes, and "it works on my machine" patches. The entire metadata parsing layer was fundamentally flawed.

So we did what any reasonable developer would do when faced with a broken codebase: we deleted everything and started over.

What We Did:

Before (v3.1.x) After (v4.0.0)
audio_parser.py (wrapper) + audio_info.py (actual parsers) → circular imports everywhere Merged everything into audio_parser.py – one file, no circular dependencies, clean and maintainable
player.py contained SDL2 loader + constants + bindings + AudioLibrary → 2000+ lines of chaos Split into player.py (AudioLibrary only) + _sdl2.py (loader + constants + bindings) – each file has a single responsibility
Import order mattered – changing imports could break things randomly Guaranteed import safety – no more "import roulette"
Debugging required tracing through 4+ files Everything is where it belongs – find it quickly

Top‑level APIs are unchanged – if you were using AudioLibrary, batch_get_metadata(), get_audio_duration(), etc., your code will continue to work exactly as before. The only breakage would occur if you were directly importing internal submodules like ap_ds.audio_info or ap_ds.player._sdl2 – which you shouldn't have been doing anyway.

And honestly? This is probably overkill for a library that gets a few hundred downloads a month – most of which are probably bots and crawlers scraping PyPI. But you know what? We'd rather rewrite it properly than leave a broken mess for the few actual humans who rely on it. So we did it anyway.


📨 A Sincere Apology – We Owe You This

We are truly, deeply sorry.

The v3.1.x series was a disaster. We shipped broken code, we didn't catch the bugs in testing, and we let down the people who trusted us. There is no excuse. We failed.

The Anatomy of Our Failure

v3.1.0 through v3.1.2 were released as LFV (Latest Feature Version) – a rapid‑release track designed to deliver new features as fast as possible to users who wanted to experiment. The idea was simple:

  • LFV = bleeding edge, new features, early adoption
  • LTS = stable, battle‑tested, production‑ready

We made this distinction clear on our website (apds.top). LFVs are deliberately less stable – they are not meant for production environments. That was the trade‑off: you get new features immediately, but you accept some risk.

However, being an LFV does not mean we should ignore bugs. It does not mean we can ship broken code and just shrug. We always fix critical issues – and we have done so with 4.0.0 – but we should have caught this earlier. We should have tested more thoroughly. We should have listened to users who reported issues sooner. We didn't. That's on us.

What We Should Have Done Differently

  1. More comprehensive testing – Our test suite didn't catch the circular import bug because it only appeared in specific environments. We should have tested across more Python versions and import configurations.

  2. Better communication – We should have been more transparent about the stability trade‑offs of LFV releases. We mentioned it, but we didn't emphasize it enough.

  3. Faster response – When users reported issues, we should have prioritized them immediately, not let them sit while we debated whether it was "our problem" or "their environment."

Our Commitment Going Forward

We are doubling down on quality and transparency:

  • v4.0.0 is a complete rewrite of the core parsing and playback architecture. We have eliminated the circular dependencies, simplified the module structure, and expanded our test suite to cover more environments.

  • v3.0.0 LTS remains the recommended version for production – it is stable, battle‑tested, and will receive security updates until March 2031. If you need reliability above all else, stick with LTS.

  • The next LTS (v4.0.0 LTS) will be released after Python 3.15 reaches stable status (expected October 2026). That release will combine the new architecture with long‑term support guarantees. Until then, v4.0.0 remains an LFV – use it if you want the latest features, but test thoroughly before deploying.

What You Should Do

Your Situation Recommendation
Production environment Continue using v3.0.0 LTS. It is stable, secure, and will remain supported until 2031. Do not upgrade to v4.0.0 unless you are willing to test thoroughly.
Development / testing Upgrade to v4.0.0 – experience the new architecture, batch parsing, and Python 3.15t support. Report any issues immediately.
Previously affected by v3.1.x bugs Must upgrade to v4.0.0 – the bug is fixed, and we guarantee it will not return.
Using Python 3.15t Must upgrade to v4.0.0 – this version fully supports GIL‑less free‑threading mode.

One Final Apology

To everyone who wasted time debugging our broken code: we are sorry.

To everyone who reported issues and got ignored: we are sorry.

To everyone who trusted us and was let down: we are sorry.

We will do better. We promise.

We won't make excuses. We'll make it right.


🚀 v4.0.0 – Performance & Batch Parsing Edition (Full Feature Set)

v4.0.0 inherits and improves upon all features introduced in v3.1.2, plus the architectural refactoring and bug fixes. It is the most capable and reliable version of ap_ds to date.

✨ New & Enhanced Features

Feature Description
Batch Parsing API batch_get_metadata(), batch_get_duration(), batch_get_metadata_by_type() – process hundreds of files in parallel with ProcessPoolExecutor.
Python 3.15t Free‑Threading Support True GIL‑less parallelism: batch parsing scales linearly on multi‑core CPUs.
O(1) DAP Deduplication _add_to_dap_recordings() now uses set‑based deduplication for near‑instant duplicate checking.
Lazy Imports (Python 3.15+) Heavy modules are loaded on demand, speeding up import ap_ds.
Runtime Self‑Check Automatic environment diagnostics on import (can be disabled with AP_DS_SKIP_AUTO_CHECK=1).
Smart WAV Mode WAV files shorter than AP_DS_WAV_THRESHOLD (default 6s) are played as sound effects; longer files are streamed as music with full seek/fade support.
Fade Controls fadein_music(), fadein_music_pos(), fadeout_music(), and status checks.
Cross‑Platform SDL2 Loader Automatic download and hash‑verified installation of SDL2 binaries on Windows and macOS; intelligent fallback on Linux.
Unified Error Handling All methods now return consistent error tuples: (error_code, error_message, suggestion). No more guessing what went wrong – every error comes with a code and actionable advice. See the Error Codes Reference section for details.

🚀 Error Handling Revolution – The Signature Improvement of v4.0.0

v4.0.0 introduces a complete, unified error-handling system — the single most impactful usability improvement in this release. Every public method now speaks one language:

(error_code, error_message, suggestion)

Why this is a game-changer:

  • Zero exception surprises — public methods never raise for expected failures. The old FileNotFoundError / RuntimeError / ValueError explosion is gone from the API surface.
  • No more None / False / 0 guessing games — every failure carries a machine-readable code and a human-readable message.
  • Actionable suggestions — every error includes a concrete next step (e.g. "Verify the file path exists and is accessible").
  • Defense in depth — invalid argument types are validated at every entry point, so a programming mistake produces a clean error tuple instead of a cryptic ctypes.ArgumentError crash deep inside the SDL bindings.
  • 18 named error codes + a full argument-validation mapping — from AP_DS_ERR_FILE_NOT_FOUND (1001) to AP_DS_ERR_UNKNOWN (1999), every condition has a name, a message, and a suggestion.

Example — a failing call is fully self-describing:

from ap_ds import AudioLibrary

lib = AudioLibrary()
result = lib.play_from_file("missing.mp3")

# result -> (1001, 'Audio file not found: missing.mp3',
#            'Verify the file path exists and is accessible')

if isinstance(result, tuple):
    code, msg, suggestion = result
    print(f"Error {code}: {msg}")
    print(f"  -> {suggestion}")

Before v4.0.0 — callers had to juggle exceptions, None, False, 0 and 0.0:

# Old v3.x behaviour (gone in v4.0.0):
try:
    aid = lib.play_from_file("missing.mp3")   # raised FileNotFoundError
except FileNotFoundError:
    pass

if lib.pause_audio(99999) is None:           # silent None
    pass

volume = lib.set_volume(aid, 200)            # returned a bool

After v4.0.0 — every call is predictable, testable, and consistent:

result = lib.play_from_file("missing.mp3")   # (1001, msg, suggestion)
result = lib.pause_audio(99999)              # (1002, msg, suggestion)
result = lib.set_volume(aid, 200)            # (1015, msg, suggestion)
result = lib.batch_get_metadata(files, max_workers=0)  # (1999, msg, suggestion)

🆕 Incremental Enhancements (v4.0.0)

1. Argument Type Validation (参数类型验证)

Every public entry point now performs strict type checking before touching the SDL layer. This guarantees that a programming mistake (passing None, a string where an int is expected, etc.) produces a clean error tuple instead of a cryptic ctypes.ArgumentError or TypeError deep inside the bindings.

Parameter Methods Accepted Types Error Returned
file_path play_from_file(), new_aid() str, bytes, os.PathLike (1001, "Invalid file path type: ...", ...)
file_path play_from_memory() str, bytes, os.PathLike (1013, "Invalid file path type: ...", ...)
loops play_from_file(), play_from_memory() int (1999, "Invalid loops type: ...", ...)
position seek_audio() int, float (1999, "Invalid position type: ...", ...)
volume set_volume() int (1015, "Invalid volume type: ...", ...)
ms, loops fadein_music(), fadein_music_pos() int (1999, "Invalid fade parameters: ...", ...)
position fadein_music_pos() int, float (1999, "Invalid fade parameters: ...", ...)

Example — invalid file path type:

lib = AudioLibrary()
print(lib.play_from_file(None))
# -> (1001, 'Invalid file path type: NoneType',
#     'file_path must be a string or os.PathLike')

print(lib.play_from_file("song.mp3", loops="2"))
# -> (1999, 'Invalid loops type: str. Expected int.',
#     'loops must be an integer (-1=infinite, 0=once, >0=count)')

Example — invalid seek position and volume:

print(lib.seek_audio(aid, None))
# -> (1999, 'Invalid position type: NoneType. Expected int or float.',
#     'Position must be a number (seconds)')

print(lib.set_volume(aid, "loud"))
# -> (1015, 'Invalid volume type: str (must be integer 0-128)',
#     'Volume must be an integer between 0 and 128')

2. Batch max_workers Error Tuple (批量解析参数错误元组)

batch_get_metadata() now catches invalid max_workers values — non-integer, <= 0, or above the platform limit — and returns a clean error tuple instead of letting a ValueError / TypeError escape to the caller.

from ap_ds import batch_get_metadata

result = batch_get_metadata(["a.mp3", "b.mp3"], max_workers=0)
# -> (1999, 'Invalid max_workers: max_workers must be greater than 0',
#     'max_workers must be a positive integer or None for automatic')

Before this enhancement, the same call raised ValueError: max_workers must be greater than 0 and crashed the program. Now the caller receives a predictable tuple and can handle it gracefully. The same applies to max_workers=1.5 (TypeError), max_workers="2" (TypeError), and max_workers=1000 (platform limit ValueError).

3. Robust SDL2 Handle Management (SDL 句柄管理增强)

player.py now explicitly imports the SDL2 library handles (_mix_lib, _sdl_lib) alongside the star-import. This makes every SDL2-backed operation (fadein_music_pos, Delay, volume, seeking) deterministic and resilient, and it is required for the new fade-in-position validation path.


⚠️ CRITICAL: Windows Batch Parsing & BrokenProcessPool

If you are using the batch parsing APIs (batch_get_metadata(), batch_get_duration(), batch_get_metadata_by_type()) on Windows, you MUST protect your entry point with if __name__ == "__main__".

Why?

On Windows, ProcessPoolExecutor uses spawn to create new processes. This means each subprocess re-imports your main module. Without the entry point guard, this creates an infinite recursion loop that crashes your program with a BrokenProcessPool error.

This is NOT optional. It is MANDATORY.

❌ WRONG (Will crash on Windows):

from ap_ds import batch_get_metadata

# This will crash on Windows with BrokenProcessPool!
results = batch_get_metadata("/music/", max_workers=4)

✅ CORRECT (Always works):

from ap_ds import batch_get_metadata

def main():
    results = batch_get_metadata("/music/", max_workers=4)
    print(f"Parsed {len(results)} files")

if __name__ == "__main__":
    main()

✅ CORRECT (For scripts with configuration):

from ap_ds import batch_get_metadata

def get_config():
    # ... configuration logic ...
    return config

def main():
    config = get_config()
    results = batch_get_metadata(config["audio_dir"], max_workers=4)
    print(f"Parsed {len(results)} files")

if __name__ == "__main__":
    main()

This applies to:

  • ✅ Any script that imports ap_ds and uses batch parsing on Windows
  • ✅ Jupyter notebooks (if running on Windows, wrap the batch call in a function and use if __name__ == "__main__")
  • ✅ Any test scripts (like CI-CD-TEST.py)

Does this apply to Linux/macOS?

No. Linux and macOS use fork by default, which does not have this issue. However, it's still good practice to use the entry point guard for cross-platform compatibility.


🎉 GIL Disabled Message

When running on Python 3.15t (free‑threading build), you will see:

🎉 ap_ds: GIL disabled (free-threading mode)

When running on standard Python (GIL enabled), you will see a warning with upgrade instructions.


⚡ Performance Comparison

Environment

Item v3.0.0 LTS v3.1.2 (with GIL) v4.0.0 (3.15t)
Python Version 3.13.4 (with GIL) 3.13.4 (with GIL) 3.15.0b4 (without GIL)
Concurrency ThreadPoolExecutor ThreadPoolExecutor ProcessPoolExecutor
Batch Parsing ❌ No ✅ Yes ✅ Yes (optimized)
Test Files 120 MP3s 120 MP3s 120 MP3s

Results

Configuration Time Speedup vs Serial
v3.0.0 (8 threads) 1.285s 0.66x ❌
v3.1.2 (8 threads) 1.367s (serial) 1.00x
Mutagen (single‑thread) 0.973s 1.00x
v4.0.0 (8 processes) 0.331s 4.13x 🚀

Key Takeaways:

  • v3.0.0 multi‑threading was bottlenecked by the GIL – more threads slowed things down.
  • v4.0.0 uses process‑level parallelism, achieving true multi‑core scaling.
  • v4.0.0 is 2.94× faster than Mutagen and 3.88× faster than v3.0.0 with 8 threads.

🧪 CI/CD – All Tests Pass (Green)

Below is the full output of the comprehensive CICD test suite (cicd_test.py) run on a Windows machine with Python 3.13.4. It covers all public APIs, error handling, edge cases, boundary values, and interactive listening tests — 421 tests passed, 0 failed, 0 skipped. The same suite has also been validated on macOS and Ubuntu with identical results; their full output is omitted here to keep the document concise.

Python 3.13.4 (tags/v3.13.4:8a526ec, Jun  3 2025, 17:46:04) [MSC v.1943 64 bit (AMD64)] on win32
Enter "help" below or click "Help" above for more information.

========================= RESTART: D:\test\cicd_test.py ========================
AP_DS © - Audio Library By DVS  v4.0.0 | https://apds.top
✅ SDL2 loaded from package directory
ℹ️ _IS_PYTHON_315_PLUS defined now: False (Python 3.13)
🎵 WAV playback mode threshold: 6s (Files >= 6s use music mode, < 6s use sound effect mode)

============================================================
 AP_DS 4.0.0 CICD Test Suite
============================================================
Enter path to an MP3 file for playback / metadata tests
(or press Enter to skip MP3-dependent tests): "C:\Users\dvs.新年快乐\Music\新建文件夹\test (4).mp3"

  Python: 3.13.4 | Platform: win32 | Mode: full
  ap_ds version: 4.0.0 | Path: C:\Users\dvs.新年快乐\AppData\Local\Programs\Python\Python313\Lib\site-packages\ap_ds
  MP3 test file: C:\Users\dvs.新年快乐\Music\新建文件夹\test (4).mp3 (exists=True)

==================================================================
 [A] Package Imports / Exports / Error Codes
==================================================================
  [PASS] ap_ds version | v4.0.0
  [PASS] AudioLibrary importable
  [PASS] get_audio_duration importable
  [PASS] get_audio_metadata importable
  [PASS] batch_get_metadata importable
  [PASS] batch_get_duration importable
  [PASS] batch_get_metadata_by_type importable
  [PASS] is_full_performance importable
  [PASS] get_runtime_info importable
  [PASS] __all__ complete | missing=[]
  [PASS] AP_DS_SUCCESS=0
  [PASS] AP_DS_ERR_FILE_NOT_FOUND=1001
  [PASS] AP_DS_ERR_INVALID_AID=1002
  [PASS] AP_DS_ERR_AUDIO_LOAD_FAILED=1003
  [PASS] AP_DS_ERR_PLAYBACK_FAILED=1004
  [PASS] AP_DS_ERR_DAP_INVALID_EXT=1009
  [PASS] AP_DS_ERR_DAP_SAVE_FAILED=1010
  [PASS] AP_DS_ERR_METADATA_PARSE_FAILED=1011
  [PASS] AP_DS_ERR_FADE_NOT_SUPPORTED=1012
  [PASS] AP_DS_ERR_AUDIO_NOT_LOADED=1013
  [PASS] AP_DS_ERR_INVALID_SOURCE=1014
  [PASS] AP_DS_ERR_INVALID_VOLUME=1015
  [PASS] AP_DS_ERR_SEEK_NOT_SUPPORTED=1016
  [PASS] AP_DS_ERR_UNKNOWN=1999
  [PASS] SDL_Init bound
  [PASS] SDL_GetError bound
  [PASS] Mix_LoadMUS bound
  [PASS] Mix_PlayMusic bound
  [PASS] Mix_SetMusicPosition bound
  [PASS] Mix_FadeInMusicPos bound
  [PASS] WAV_THRESHOLD=6 | got=6

==================================================================
 [B] Metadata Parsing (WAV/MP3 + batch)
==================================================================
  [PASS] WAV short duration=2s | got=2
  [PASS] WAV long duration=10s | got=10
  [PASS] WAV metadata is dict
  [PASS] WAV sample_rate=22050 | got=22050
  [PASS] WAV channels=1 | got=1
  [PASS] WAV fields complete
  [PASS] MP3 duration>0 | got=198
  [PASS] MP3 metadata is dict
  [PASS] MP3 format=mp3 | got=mp3
  [PASS] MP3 duration field>0 | got=198
  [PASS] Corrupted WAV duration=0 | got=0
  [PASS] Corrupted WAV metadata=None | got=None
  [PASS] batch_get_metadata returns 2 | got=2
  [PASS] batch_get_duration returns 2 | got={'D:\\test\\cicd_tmp\\t_short.wav': 2, 'D:\\test\\cicd_tmp\\t_long.wav': 10}
  [PASS] batch_by_type filters wav=2 | got=2
⚠️  Parse failed: t_bad.wav
  [PASS] batch with corrupted -> 1 kept | got=1
⚠️  Parse failed: empty.aac
⚠️  Parse failed: empty.flac
⚠️  Parse failed: empty.mp3
⚠️  Parse failed: empty.ogg
⚠️  Parse failed: t_bad.wav
  [PASS] batch on directory >0 | got=5
  [PASS] Unsupported format raises ValueError

==================================================================
 [C] AudioLibrary Initialization
==================================================================
  [PASS] Default init ok
  [PASS] AID counter starts at 0
  [PASS] Caches initially empty
  [PASS] DAP initially empty
  [PASS] MUS_NO_FADING=0
  [PASS] Custom-param init ok
  [PASS] frequency stored=48000
  [PASS] channels stored=1

==================================================================
 [D] Playback
==================================================================
📝 Recorded DAP file: D:\test\cicd_tmp\t_short.wav
  [PASS] play_from_file(short WAV) returns AID | got=1
  [PASS] short WAV is_music_playing=False
  [PASS] short WAV in audio_cache
📝 Recorded DAP file: D:\test\cicd_tmp\t_long.wav
  [PASS] play_from_file(long WAV) returns AID | got=2
  [PASS] long WAV is_music_playing=True
  [PASS] long WAV in music_cache
  [PASS] play_from_file(start_pos=3) returns AID | got=3
  [PASS] Missing file -> 1001 | got=(1001, 'Audio file not found: D:\\test\\cicd_tmp\\missing.mp3', 'Verify the file path exists and is accessible')
  [PASS] .ap-ds-dap -> 1003 | got=(1003, 'Failed to load audio file: D:\\test\\cicd_tmp\\t.ap-ds-dap', 'Check file format and integrity')
  [PASS] play_from_memory(not loaded) -> 1013 | got=(1013, 'Audio not loaded in memory: D:\\test\\cicd_tmp\\never_loaded.wav', 'Call new_aid() first to load the file')
  [PASS] new_aid returns AID | got=6
  [PASS] play_from_memory after new_aid ok | got=7
  [PASS] new_aid(missing) -> 1001 | got=(1001, 'Audio file not found: D:\\test\\cicd_tmp\\missing.wav', 'Verify the file path exists and is accessible')

==================================================================
 [E] Playback Control
==================================================================
  [PASS] Play long WAV ok | got=8
  [PASS] pause_audio ok | got=(0, '', '')
  [PASS] is_music_paused=True
  [PASS] play_audio resume ok | got=(0, '', '')
  [PASS] is_music_playing=True after resume
  [PASS] stop_audio returns float | got=0.5987825393676758
  [PASS] pause_audio(invalid AID) -> 1002 | got=(1002, 'Invalid AID: 99999', 'Check that the AID is valid and the audio is loaded')
  [PASS] play_audio(invalid AID) -> 1002 | got=(1002, 'Invalid AID: 99999', 'Check that the AID is valid and the audio is loaded')
  [PASS] stop_audio(invalid AID) -> 1002 | got=(1002, 'Invalid AID: 99999', 'Check that the AID is valid and the audio is loaded')
  [PASS] seek_audio(invalid AID) -> 1002 | got=(1002, 'Invalid AID: 99999', 'Check that the AID is valid and the audio is loaded')

==================================================================
 [F] Volume Control
==================================================================
  [PASS] set_volume(music,64) ok | got=(0, '', '')
  [PASS] get_volume(music) is int | got=64
  [PASS] set_volume(sound,100) ok | got=(0, '', '')
  [PASS] get_volume(sound) is int | got=100
  [PASS] set_volume(-1) -> 1015 | got=(1015, 'Invalid volume: -1 (must be 0-128)', 'Volume range is 0-128')
  [PASS] set_volume(129) -> 1015 | got=(1015, 'Invalid volume: 129 (must be 0-128)', 'Volume range is 0-128')
  [PASS] set_volume(invalid AID) -> 1002 | got=(1002, 'Invalid AID: 99999', 'Check that the AID is valid and the audio is loaded')
  [PASS] get_volume(invalid AID) -> 1002 | got=(1002, 'Invalid AID: 99999', 'Check that the AID is valid and the audio is loaded')

==================================================================
 [G] Seek
==================================================================
  [PASS] seek_audio(music,5s) ok | got=(0, '', '')
  [PASS] music still playing after seek
  [PASS] seek_audio(sound) ok | got=(0, '', '')
  [PASS] sound entry retained after seek
  [PASS] sound controllable after seek | got=(0, '', '')

==================================================================
 [H] Fade In / Out
==================================================================
  [PASS] fadein_music ok | got=(0, '', '')
  [PASS] get_music_fading returns 0/1/2 | got=2
  [PASS] fadeout_music ok | got=(0, '', '')
  [PASS] fadein_music_pos ok | got=(0, '', '')
  [PASS] fadein_music(invalid AID) -> 1002 | got=(1002, 'AID 99999 not found or not a music file', 'Verify the AID corresponds to a music file')
  [PASS] fadein_music_pos(invalid AID) -> 1002 | got=(1002, 'AID 99999 not found or not a music file', 'Verify the AID corresponds to a music file')

==================================================================
 [I] DAP Recording System
==================================================================
🗑️ Cleared all DAP recordings
  [PASS] clear_dap empties list
📝 Recorded DAP file: D:\test\cicd_tmp\t_short.wav
📝 Recorded DAP file: D:\test\cicd_tmp\t_long.wav
  [PASS] DAP records 2 entries | got=2
  [PASS] DAP dedupe keeps 2 | got=2
  [PASS] DAP record fields complete
  [PASS] DAP record missing file no crash
✅ Saved 2 DAP records to: D:\test\cicd_tmp\out.ap-ds-dap
  [PASS] save_dap_to_json ok | got=(0, '', '')
  [PASS] DAP file created
  [PASS] save_dap_to_json(.json) -> 1009 | got=(1009, "Invalid file extension. Expected '.ap-ds-dap' but got '.json'", 'Use .ap-ds-dap extension for DAP files')
  [PASS] save_dap_to_json(bad path) -> 1010 | got=(1010, "Error saving DAP to JSON: [Errno 2] No such file or directory: 'Z:\\no\\such\\dir\\out.ap-ds-dap'", 'Check write permissions and disk space')
🗑️ Cleared all DAP recordings
  [PASS] clear_dap again empties

==================================================================
 [J] Metadata Methods
==================================================================
📝 Recorded DAP file: D:\test\cicd_tmp\t_long.wav
  [PASS] get_audio_metadata_by_aid is dict | got=dict
  [PASS] get_audio_metadata_by_path is dict | got=dict
  [PASS] get_audio_metadata(str path) is dict | got=dict
  [PASS] get_audio_metadata(int AID) is dict | got=dict
  [PASS] get_audio_metadata(float) -> 1014 | got=(1014, "Invalid source type: <class 'float'>. Expected str or int.", 'Use file path (str) or AID (int)')
  [PASS] get_audio_duration(AID)=10 | got=10
  [PASS] get_audio_duration(path)=10 | got=10
  [PASS] get_audio_duration(invalid AID) -> 1002 | got=(1002, 'Invalid AID: 99999', 'Check that the AID is valid')
  [PASS] _get_sample_rate=22050 | got=22050
  [PASS] _get_channels=1 | got=1
  [PASS] simple_mp3_duration_estimation>0 | got=27.43725
  [PASS] _get_playing_duration>=10 | got=10.0
  [PASS] _get_file_duration=10 | got=10.0
  [PASS] get_audio_metadata_by_aid(invalid) -> 1002 | got=(1002, 'Invalid AID: 99999', 'Check that the AID is valid')
  [PASS] get_audio_metadata_by_path(missing) -> 1001 | got=(1001, 'File not found: D:\\test\\cicd_tmp\\missing.mp3', 'Verify the file path exists')
  [PASS] get_audio_duration(missing path) -> 1001 | got=(1001, 'File not found: D:\\test\\cicd_tmp\\missing.mp3', 'Verify the file path exists')

==================================================================
 [K] Helper Methods
==================================================================
  [PASS] _is_music_file(.mp3)=True
  [PASS] _is_music_file(.ogg)=True
  [PASS] _is_music_file(.flac)=True
  [PASS] _is_music_file(long wav)=True
  [PASS] _is_music_file(short wav)=False
  [PASS] _is_music_file(.aif)=False
  [PASS] _is_music_file(.txt)=False
  [PASS] _find_channel_by_aid found | got=-1
  [PASS] _find_channel_by_aid(invalid)=None
  [PASS] _get_file_path_by_aid returns path | got=D:\test\cicd_tmp\t_long.wav
  [PASS] _get_file_path_by_aid(invalid) -> 1002 | got=(1002, 'Invalid AID: 99999', 'Check that the AID is valid')
  [PASS] _get_aid_for_music found | got=16
📝 Recorded DAP file: D:\test\cicd_tmp\t_short.wav
  [PASS] _get_aid_for_audio found | got=17
  [PASS] _get_aid_for_audio(music file) -> 1002 | got=(1002, 'No AID found for file: D:\\test\\cicd_tmp\\t_long.wav', 'File may not be loaded or is a music file')

==================================================================
 [L] Resource Management
==================================================================
  [PASS] clear_memory_cache empties caches
  [PASS] cleanup_function completes

==================================================================
 [M] Top-Level API
==================================================================
  [PASS] get_runtime_info is dict | got=dict
  [PASS] is_full_performance is bool | got=False

==================================================================
 [O] Edge Cases & Error Handling
==================================================================
  --- O1 invalid argument types ---
  [PASS] play_from_file(NoneType) -> tuple(1001) | got=(1001, 'Invalid file path type: NoneType', 'file_path must be a string or os.PathLike')
  [PASS] play_from_file(float) -> tuple(1001) | got=(1001, 'Invalid file path type: float', 'file_path must be a string or os.PathLike')
  [PASS] play_from_file(list) -> tuple(1001) | got=(1001, 'Invalid file path type: list', 'file_path must be a string or os.PathLike')
  [PASS] play_from_file(dict) -> tuple(1001) | got=(1001, 'Invalid file path type: dict', 'file_path must be a string or os.PathLike')
  [PASS] play_from_file(tuple) -> tuple(1001) | got=(1001, 'Invalid file path type: tuple', 'file_path must be a string or os.PathLike')
  [PASS] play_from_memory(list) -> tuple | got=(1013, 'Invalid file path type: list', 'file_path must be a string or os.PathLike')
  [PASS] play_from_memory(dict) -> tuple | got=(1013, 'Invalid file path type: dict', 'file_path must be a string or os.PathLike')
  [PASS] new_aid(NoneType) -> tuple(1001) | got=(1001, 'Invalid file path type: NoneType', 'file_path must be a string or os.PathLike')
  [PASS] new_aid(float) -> tuple(1001) | got=(1001, 'Invalid file path type: float', 'file_path must be a string or os.PathLike')
  [PASS] new_aid(list) -> tuple(1001) | got=(1001, 'Invalid file path type: list', 'file_path must be a string or os.PathLike')
  --- O2 boundary values ---
📝 Recorded DAP file: D:\test\cicd_tmp\t_long.wav
  [PASS] seek_audio(None) -> tuple | got=(1999, 'Invalid position type: NoneType. Expected int or float.', 'Position must be a number (seconds)')
  [PASS] seek_audio('5') -> tuple | got=(1999, 'Invalid position type: str. Expected int or float.', 'Position must be a number (seconds)')
  [PASS] seek_audio(3.0) -> success | got=(0, '', '')
  [PASS] set_volume(None) -> 1015 | got=(1015, 'Invalid volume type: NoneType (must be integer 0-128)', 'Volume must be an integer between 0 and 128')
  [PASS] set_volume('50') -> 1015 | got=(1015, 'Invalid volume type: str (must be integer 0-128)', 'Volume must be an integer between 0 and 128')
  [PASS] set_volume(1.5) -> 1015 | got=(1015, 'Invalid volume type: float (must be integer 0-128)', 'Volume must be an integer between 0 and 128')
  [PASS] set_volume(0) -> success | got=(0, '', '')
  [PASS] set_volume(128) -> success | got=(0, '', '')
  [PASS] set_volume(-1) -> 1015 | got=(1015, 'Invalid volume: -1 (must be 0-128)', 'Volume range is 0-128')
  [PASS] set_volume(129) -> 1015 | got=(1015, 'Invalid volume: 129 (must be 0-128)', 'Volume range is 0-128')
  [PASS] fadein_music(ms=None) -> tuple | got=(1999, 'Invalid fade parameters: ms and loops must be integers', 'Check fade parameters (ms: int milliseconds, loops: int)')
  [PASS] fadein_music_pos(position=None) -> tuple | got=(1999, 'Invalid fade parameters: ms/loops must be int, position must be a number', 'Check fade parameters')
  --- O3 exact error codes ---
  [PASS] 1001 missing file -> 1001 | got=(1001, 'Audio file not found: D:\\test\\cicd_tmp\\no_such_dir.mp3', 'Verify the file path exists and is accessible')
  [PASS] 1002 invalid AID -> 1002 | got=(1002, 'Invalid AID: 99999', 'Check that the AID is valid and the audio is loaded')
  [PASS] 1003 load failure (DAP file) -> 1003 | got=(1003, 'Failed to load audio file: D:\\test\\cicd_tmp\\t.ap-ds-dap', 'Check file format and integrity')
  [PASS] 1009 DAP bad extension -> 1009 | got=(1009, "Invalid file extension. Expected '.ap-ds-dap' but got '.json'", 'Use .ap-ds-dap extension for DAP files')
  [PASS] 1010 DAP save failure -> 1010 | got=(1010, "Error saving DAP to JSON: [Errno 2] No such file or directory: 'Z:\\no\\such\\dir\\x.ap-ds-dap'", 'Check write permissions and disk space')
  [PASS] 1011 metadata parse failure -> 1011 | got=(1011, 'Failed to parse metadata for: D:\\test\\cicd_tmp\\t_bad.wav', 'File may be corrupted or unsupported')
  [PASS] 1013 not loaded in memory -> 1013 | got=(1013, 'Audio not loaded in memory: D:\\test\\cicd_tmp\\x.wav', 'Call new_aid() first to load the file')
  [PASS] 1014 invalid source type -> 1014 | got=(1014, "Invalid source type: <class 'float'>. Expected str or int.", 'Use file path (str) or AID (int)')
  [PASS] 1015 invalid volume -> 1015 | got=(1015, 'Invalid volume: 200 (must be 0-128)', 'Volume range is 0-128')
  --- O4 valid return values ---
📝 Recorded DAP file: D:\test\cicd_tmp\t_short.wav
  [PASS] play_from_file(short WAV) -> int AID | got=5
  [PASS] get_audio_duration(path) -> valid int>0 | got=10
  [PASS] get_audio_metadata_by_path -> dict complete | got={'path': 'D:\\test\\cicd_tmp\\t_short.wav', 'format': 'wav', 'duration': 2, 'length': 2.0, 'sample_rate': 22050, 'channels': 1, 'bitrate': 352800}
🗑️ Cleared all DAP recordings
📝 Recorded DAP file: D:\test\cicd_tmp\t_short.wav
✅ Saved 1 DAP records to: D:\test\cicd_tmp\edge.ap-ds-dap
  [PASS] save_dap_to_json -> (0,'','') | got=(0, '', '')
  [PASS] batch_get_metadata -> valid list | got=[{'path': 'D:\\test\\cicd_tmp\\t_short.wav', 'format': 'wav', 'duration': 2, 'length': 2.0, 'sample_rate': 22050, 'channels': 1, 'bitrate': 352800}, {'path': 'D:\\test\\cicd_tmp\\t_long.wav', 'format': 'wav', 'duration': 10, 'length': 10.0, 'sample_rate': 22050, 'channels': 1, 'bitrate': 352800}]
  [PASS] batch_get_metadata([]) -> [] | got=[]
  [PASS] batch_get_metadata(None) -> [] | got=[]
  [PASS] batch max_workers=0 -> error tuple(1999) | got=(1999, 'Invalid max_workers: max_workers must be greater than 0', 'max_workers must be a positive integer or None for automatic')
  [PASS] batch max_workers=-1 -> error tuple(1999) | got=(1999, 'Invalid max_workers: max_workers must be greater than 0', 'max_workers must be a positive integer or None for automatic')
  [PASS] batch max_workers=1.5 -> error tuple(1999) | got=(1999, "Invalid max_workers: 'float' object cannot be interpreted as an integer", 'max_workers must be a positive integer or None for automatic')
  [PASS] batch max_workers=2 -> error tuple(1999) | got=(1999, "Invalid max_workers: '<=' not supported between instances of 'str' and 'int'", 'max_workers must be a positive integer or None for automatic')
  [PASS] batch max_workers=1000 -> error tuple(1999) | got=(1999, 'Invalid max_workers: max_workers must be <= 61', 'max_workers must be a positive integer or None for automatic')

==================================================================
 [P] __init__.py Module Coverage
==================================================================
  [PASS] __version__ = 4.0.0 | 4.0.0
  [PASS] top-level get_audio_duration callable
  [PASS] top-level get_audio_metadata callable
  [PASS] top-level batch_get_metadata callable
  [PASS] top-level batch_get_duration callable
  [PASS] top-level batch_get_metadata_by_type callable
  [PASS] top-level is_full_performance callable
  [PASS] top-level get_runtime_info callable
  [PASS] top-level auto_check_runtime callable
  [PASS] top-level check_runtime_mode callable
  [PASS] top-level show_tech_manual callable
  [PASS] __all__ contains __version__
  [PASS] __all__ contains AudioLibrary
  [PASS] __all__ contains get_audio_duration
  [PASS] __all__ contains get_audio_metadata
  [PASS] __all__ contains batch_get_metadata
  [PASS] __all__ contains batch_get_duration
  [PASS] __all__ contains batch_get_metadata_by_type
  [PASS] __all__ contains auto_check_runtime
  [PASS] __all__ contains check_runtime_mode
  [PASS] __all__ contains show_tech_manual
  [PASS] show_tech_manual output >1000 chars | 16303
  [PASS] manual contains 'TECHNICAL MANUAL'
  [PASS] manual contains 'AudioLibrary'
  [PASS] manual contains 'AP_DS_WAV_THRESHOLD'
  [PASS] manual contains 'DAP'
  [PASS] manual contains 'SDL2'
  [PASS] _RUNTIME_CHECKED=True after import
  [PASS] SUPPRESS_WARNINGS matches env | module=True env=True
  [PASS] SHOW_CONGRATS matches env | module=True env=True
  [PASS] _AUTO_CHECK_SKIP matches env | module=True env=True
  [PASS] ensure_runtime_checked sets True
  [PASS] ensure_runtime_checked idempotent
  [PASS] check_runtime_mode returns bool | True
  [PASS] _check_runtime_mode returns bool | True
  [PASS] check_runtime_mode == _check_runtime_mode
  [PASS] auto_check_runtime(SKIP) returns None
  [PASS] _auto_check_runtime(SKIP) returns None
  [PASS] get_runtime_info(SKIP) returns {}
  [PASS] is_full_performance(SKIP) returns False
  [PASS] _auto_check_runtime(SKIP=0) returns dict
  [PASS] auto dict 11 keys complete | missing=[]
  [PASS] info[library_name]=AP_DS
  [PASS] info[library_version]=4.0.0
  [PASS] info[gil_enabled] is bool
  [PASS] info[has_profiling] is bool
  [PASS] info[is_full_performance] is bool
  [PASS] info[cpu_count]>0
  [PASS] info[platform]=win32
  [PASS] self-check prints 'Runtime Self-Check'
  [PASS] self-check prints 'Python'
  [PASS] self-check prints 'GIL'
  [PASS] self-check prints 'CPU Cores'

============================================================
🔍 ap_ds Runtime Self-Check
============================================================
📚 Library: AP_DS (Audio Library By DVS)
📌 Version: 4.0.0
📂 Install Path: C:\Users\dvs.新年快乐\AppData\Local\Programs\Python\Python313\Lib\site-packages\ap_ds
🌐 Website: https://apds.top
📦 PyPI: https://pypi.org/project/ap-ds/
📦 Mirror: https://pypi.tuna.tsinghua.edu.cn/simple/ap-ds/

📥 Installation:
   pip install ap-ds==4.0.0
   pip install ap-ds==4.0.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
   pip install /path/to/ap-ds-4.0.0-py3-none-any.whl
👤 Author: DVS

📖 Description:
   AP_DS (Audio Playback & Data Service) is a cross-platform audio
   library built on SDL2 and SDL2_mixer, designed for Python applications
   requiring high-performance audio playback and metadata management.

   Core Features:
   • Audio Playback: MP3, WAV, FLAC, OGG, AAC, and more
   • Smart WAV Handling: Auto-switch between music/sound effect mode
   • Metadata Parsing: Duration, sample rate, channels, bitrate
   • DAP Recording: O(1) deduplication playlist generation
   • Batch Processing: Multi-core parallel metadata extraction
   • Fade Control: Fade in/out with position seeking support
   • Memory Management: Efficient caching with automatic cleanup

   Performance:
   • Native SDL2 bindings with zero-copy audio processing
   • Free-threading support (Python 3.15t) for maximum parallelism
   • ProcessPoolExecutor for CPU-bound batch operations

   Platform Support:
   • Windows (x64)  • macOS (x64/ARM)  • Linux (x64/ARM)

   Documentation: https://apds.top/docs
   Source Code: https://gitcode.com/dvsxt/ap_ds
   License: DVS Audio Library (ap_ds) Open Source License Version 2.0

🐍 Python: 3.13.4 (cpython)
🔒 GIL: Enabled
📊 Profiling: Not available (requires Python 3.15+)
🚀 Full Performance Mode: ❌ No (degraded mode)
💻 CPU Cores: 16
🖥️  Platform: win32
============================================================
💡 Tip: Upgrade to Python 3.15t for full performance:
   https://mirrors.huaweicloud.com/python/3.15.0/python-3.15.0b4t-amd64.zip
   To suppress this auto-check, set AP_DS_SKIP_AUTO_CHECK=1
============================================================

  [PASS] get_runtime_info(SKIP=0) returns dict
  [PASS] get_runtime_info keys complete | keys=['library_name', 'library_version', 'library_install_path', 'library_website', 'library_author', 'python_version', 'gil_enabled', 'has_profiling', 'is_full_performance', 'cpu_count', 'platform']
  [PASS] get_runtime_info[library_name]=AP_DS
  [PASS] get_runtime_info same source as auto

============================================================
🔍 ap_ds Runtime Self-Check
============================================================
📚 Library: AP_DS (Audio Library By DVS)
📌 Version: 4.0.0
📂 Install Path: C:\Users\dvs.新年快乐\AppData\Local\Programs\Python\Python313\Lib\site-packages\ap_ds
🌐 Website: https://apds.top
📦 PyPI: https://pypi.org/project/ap-ds/
📦 Mirror: https://pypi.tuna.tsinghua.edu.cn/simple/ap-ds/

📥 Installation:
   pip install ap-ds==4.0.0
   pip install ap-ds==4.0.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
   pip install /path/to/ap-ds-4.0.0-py3-none-any.whl
👤 Author: DVS

📖 Description:
   AP_DS (Audio Playback & Data Service) is a cross-platform audio
   library built on SDL2 and SDL2_mixer, designed for Python applications
   requiring high-performance audio playback and metadata management.

   Core Features:
   • Audio Playback: MP3, WAV, FLAC, OGG, AAC, and more
   • Smart WAV Handling: Auto-switch between music/sound effect mode
   • Metadata Parsing: Duration, sample rate, channels, bitrate
   • DAP Recording: O(1) deduplication playlist generation
   • Batch Processing: Multi-core parallel metadata extraction
   • Fade Control: Fade in/out with position seeking support
   • Memory Management: Efficient caching with automatic cleanup

   Performance:
   • Native SDL2 bindings with zero-copy audio processing
   • Free-threading support (Python 3.15t) for maximum parallelism
   • ProcessPoolExecutor for CPU-bound batch operations

   Platform Support:
   • Windows (x64)  • macOS (x64/ARM)  • Linux (x64/ARM)

   Documentation: https://apds.top/docs
   Source Code: https://gitcode.com/dvsxt/ap_ds
   License: DVS Audio Library (ap_ds) Open Source License Version 2.0

🐍 Python: 3.13.4 (cpython)
🔒 GIL: Enabled
📊 Profiling: Not available (requires Python 3.15+)
🚀 Full Performance Mode: ❌ No (degraded mode)
💻 CPU Cores: 16
🖥️  Platform: win32
============================================================
💡 Tip: Upgrade to Python 3.15t for full performance:
   https://mirrors.huaweicloud.com/python/3.15.0/python-3.15.0b4t-amd64.zip
   To suppress this auto-check, set AP_DS_SKIP_AUTO_CHECK=1
============================================================

  [PASS] is_full_performance(SKIP=0) is bool
  [PASS] is_full_performance matches info
  [PASS] after restore auto_check_runtime returns None
  [PASS] after restore get_runtime_info returns {}

==================================================================
 [Q] _sdl2.py Constants / Structures / Bindings
==================================================================
  [PASS] constant SDL_TRUE=1 | got=1
  [PASS] constant SDL_FALSE=0 | got=0
  [PASS] constant SDL_INIT_TIMER=1 | got=1
  [PASS] constant SDL_INIT_AUDIO=16 | got=16
  [PASS] constant SDL_INIT_VIDEO=32 | got=32
  [PASS] constant SDL_INIT_JOYSTICK=512 | got=512
  [PASS] constant SDL_INIT_HAPTIC=4096 | got=4096
  [PASS] constant SDL_INIT_GAMECONTROLLER=8192 | got=8192
  [PASS] constant SDL_INIT_EVENTS=16384 | got=16384
  [PASS] constant AUDIO_U8=8 | got=8
  [PASS] constant AUDIO_S8=32776 | got=32776
  [PASS] constant AUDIO_U16LSB=16 | got=16
  [PASS] constant AUDIO_S16LSB=32784 | got=32784
  [PASS] constant AUDIO_U16MSB=4112 | got=4112
  [PASS] constant AUDIO_S16MSB=36880 | got=36880
  [PASS] constant AUDIO_S32LSB=32800 | got=32800
  [PASS] constant AUDIO_S32MSB=36896 | got=36896
  [PASS] constant AUDIO_F32LSB=33056 | got=33056
  [PASS] constant AUDIO_F32MSB=37152 | got=37152
  [PASS] constant MIX_INIT_FLAC=1 | got=1
  [PASS] constant MIX_INIT_MOD=2 | got=2
  [PASS] constant MIX_INIT_MP3=8 | got=8
  [PASS] constant MIX_INIT_OGG=16 | got=16
  [PASS] constant MIX_INIT_MID=32 | got=32
  [PASS] constant MIX_INIT_OPUS=64 | got=64
  [PASS] constant MIX_CHANNEL_POST=-2 | got=-2
  [PASS] constant MIX_DEFAULT_CHANNELS=2 | got=2
  [PASS] constant MUS_NONE=0 | got=0
  [PASS] constant MUS_CMD=1 | got=1
  [PASS] constant MUS_WAV=2 | got=2
  [PASS] constant MUS_MOD=3 | got=3
  [PASS] constant MUS_MID=4 | got=4
  [PASS] constant MUS_OGG=5 | got=5
  [PASS] constant MUS_MP3=6 | got=6
  [PASS] constant MUS_FLAC=7 | got=7
  [PASS] constant MUS_OPUS=8 | got=8
  [PASS] MIX_DEFAULT_FORMAT==AUDIO_S16SYS
  [PASS] SDL_INIT_EVERYTHING combination
  [PASS] AUDIO_S16SYS is S16LSB on little-endian
  [PASS] SDL_AudioSpec.freq field
  [PASS] SDL_AudioSpec.format field
  [PASS] SDL_AudioSpec.channels field
  [PASS] SDL_AudioSpec.silence field
  [PASS] SDL_AudioSpec.samples field
  [PASS] SDL_AudioSpec.padding field
  [PASS] SDL_AudioSpec.size field
  [PASS] SDL_AudioSpec.callback field
  [PASS] SDL_AudioSpec.userdata field
  [PASS] Mix_Chunk.allocated field
  [PASS] Mix_Chunk.abuf field
  [PASS] Mix_Chunk.alen field
  [PASS] Mix_Chunk.volume field
  [PASS] _sdl_lib loaded
  [PASS] _mix_lib loaded
  [PASS] import_sdl2 returns 2-tuple
  [PASS] _check_sdl2_loaded() True
  [PASS] _check_sdl_libraries_exist(package dir)
  [PASS] binding SDL_Init exists
  [PASS] binding SDL_Quit exists
  [PASS] binding SDL_GetError exists
  [PASS] binding SDL_RWFromFile exists
  [PASS] binding SDL_Delay exists
  [PASS] binding Mix_OpenAudio exists
  [PASS] binding Mix_CloseAudio exists
  [PASS] binding Mix_LoadWAV exists
  [PASS] binding Mix_LoadMUS exists
  [PASS] binding Mix_FreeChunk exists
  [PASS] binding Mix_FreeMusic exists
  [PASS] binding Mix_PlayChannel exists
  [PASS] binding Mix_PlayMusic exists
  [PASS] binding Mix_Pause exists
  [PASS] binding Mix_PauseMusic exists
  [PASS] binding Mix_Resume exists
  [PASS] binding Mix_ResumeMusic exists
  [PASS] binding Mix_HaltChannel exists
  [PASS] binding Mix_HaltMusic exists
  [PASS] binding Mix_SetMusicPosition exists
  [PASS] binding Mix_MusicDuration exists
  [PASS] binding Mix_Volume exists
  [PASS] binding Mix_VolumeMusic exists
  [PASS] binding Mix_AllocateChannels exists
  [PASS] binding Mix_GetMusicType exists
  [PASS] binding Mix_FadingMusic exists
  [PASS] binding Mix_FadeInMusic exists
  [PASS] binding Mix_FadeOutMusic exists
  [PASS] binding Mix_FadeInChannel exists
  [PASS] binding Mix_FadeOutChannel exists
  [PASS] binding Mix_Playing exists
  [PASS] binding Mix_PlayingMusic exists
  [PASS] binding Mix_Paused exists
  [PASS] binding Mix_PausedMusic exists
  [PASS] binding Mix_SetPanning exists
  [PASS] binding Mix_SetDistance exists
  [PASS] binding Mix_SetPosition exists
  [PASS] binding Mix_SetReverseStereo exists
  [PASS] binding Mix_FadeInMusicPos exists
  [PASS] binding _load_from_directory exists
  [PASS] binding _load_from_system exists
  [PASS] binding _load_user_config exists
  [PASS] binding _linux_auto_install exists
  [PASS] binding _linux_interactive_setup exists
  [PASS] binding _check_sdl_libraries_exist exists
  [PASS] binding _check_sdl2_loaded exists
  [PASS] binding import_sdl2 exists
  [PASS] binding _setup_prototypes exists
  [PASS] SDL_Init.argtypes=[c_uint32]
  [PASS] SDL_GetError.restype=c_char_p
  [PASS] Mix_OpenAudio.argtypes set

==================================================================
 [R] audio_parser.py Deep Parser Coverage
==================================================================
  [PASS] StreamInfo.length=10.5
  [PASS] StreamInfo.sample_rate=44100
  [PASS] StreamInfo.channels=2
  [PASS] StreamInfo.bitrate=128000
  [PASS] StreamInfo repr contains StreamInfo
  [PASS] FileType._parse raises ValueError
  [PASS] read_u32_be(0x0100)
  [PASS] read_u32_le(0x0100)
  [PASS] read_u16_le(0x0100)
  [PASS] open_audio(.wav) -> WAVFile
  [PASS] open_audio(.mp3) -> MP3File
  [PASS] open_audio(.txt) raises ValueError
  [PASS] open_audio(.xyz) raises ValueError
  [PASS] open_audio(.flac_bad) raises ValueError
  [PASS] metadata 16bit mono (2s/22050/1ch) | got={'path': 'D:\\test\\cicd_tmp\\t_short.wav', 'format': 'wav', 'duration': 2, 'length': 2.0, 'sample_rate': 22050, 'channels': 1, 'bitrate': 352800}
  [PASS] metadata 16bit stereo (2s/22050/2ch) | got={'path': 'D:\\test\\cicd_tmp\\r_stereo.wav', 'format': 'wav', 'duration': 2, 'length': 2.0, 'sample_rate': 22050, 'channels': 2, 'bitrate': 352800}
  [PASS] metadata 10s long (10s/22050/1ch) | got={'path': 'D:\\test\\cicd_tmp\\t_long.wav', 'format': 'wav', 'duration': 10, 'length': 10.0, 'sample_rate': 22050, 'channels': 1, 'bitrate': 352800}
  [PASS] Constructed FLAC parses (10s/44100/2ch) | got={'path': 'D:\\test\\cicd_tmp\\r_test.flac', 'format': 'flac', 'duration': 10, 'length': 10.0, 'sample_rate': 44100, 'channels': 2, 'bitrate': 33}
  [PASS] empty .flac -> 0 | got=0
  [PASS] empty .aac -> 0 | got=0
  [PASS] empty .ogg -> 0 | got=0
  [PASS] empty .mp3 -> 0 | got=0
  [PASS] by_type filters flac -> 1 | got=1
  [PASS] batch mixed formats -> 3 | got=3

==================================================================
 [S] Supplementary Cases
==================================================================
  --- 1. init boundary parameters ---
  [PASS] init(frequency=0) accepted
  [PASS] init(frequency=-44100) rejected by SDL
  [PASS] init(channels=0) accepted
  [PASS] init(channels=9) rejected by SDL
  [PASS] init(channels=32) rejected by SDL
  [PASS] init(chunksize=0) accepted
  [PASS] init(chunksize=-1) accepted
  [PASS] init(format=0) accepted
  [PASS] init(format=0xFFFF) rejected by SDL
  --- 2. loops parameter ---
📝 Recorded DAP file: D:\test\cicd_tmp\t_long.wav
  [PASS] play_from_file(loops=-1) returns AID | got=1
  [PASS] loops=-1 still playing
  [PASS] play_from_file(loops=2) returns AID | got=2
📝 Recorded DAP file: D:\test\cicd_tmp\t_short.wav
  [PASS] play_from_file(short WAV, loops=-1) returns AID | got=3
  [PASS] play_from_file(loops='2') no crash | got=(1999, 'Invalid loops type: str. Expected int.', 'loops must be an integer (-1=infinite, 0=once, >0=count)')
  [PASS] play_from_file(loops=None) no crash | got=(1999, 'Invalid loops type: NoneType. Expected int.', 'loops must be an integer (-1=infinite, 0=once, >0=count)')
  [PASS] play_from_memory(loops=-1) returns AID | got=5
  --- 3. batch show_progress output ---
  [PASS] show_progress returns list(2) | got=2
  [PASS] show_progress prints 'Batch parse complete' | ✅ Batch parse complete: 2/2 files successful

  --- 4. Delay method ---
  [PASS] Delay(100) ~100ms | 0.101s
  [PASS] Delay(0) fast
  --- 5. FileType base class ---
  [PASS] FileType.filename
  [PASS] FileType.info is StreamInfo
  [PASS] FileType.length property | 10.0
  [PASS] FileType.sample_rate property | 22050
  [PASS] FileType.channels property | 1
  [PASS] FileType.bitrate property | 352800
  --- 6. _get_sample_rate / _get_channels ---
  [PASS] _get_sample_rate(AID)=22050 | 22050
  [PASS] _get_channels(AID)=1 | 1
  [PASS] _get_sample_rate(invalid)=44100 fallback
  [PASS] _get_channels(invalid)=2 fallback
  --- 7. _get_aid_for_* success paths ---
  [PASS] _get_aid_for_music success | got=7
  [PASS] _get_aid_for_audio success | got=8
  [PASS] _get_aid_for_music(sound file) -> 1002 | got=(1002, 'No AID found for music file: D:\\test\\cicd_tmp\\t_short.wav', 'File may not be loaded or is not a music file')
  [PASS] _get_aid_for_audio(music file) -> 1002 | got=(1002, 'No AID found for file: D:\\test\\cicd_tmp\\t_long.wav', 'File may not be loaded or is a music file')

==================================================================
 [N] Listening Tests (please put on headphones / turn on speakers)
==================================================================

  Now playing at default volume 128 for 3 seconds...
📝 Recorded DAP file: C:\Users\dvs.新年快乐\Music\新建文件夹\test (4).mp3
  >>> Did you hear clear, normal-volume music? (y/n): y
  [PASS] Listening 1: normal playback audible

  Volume set to 40/128, playing for 2 seconds...
  >>> Was the volume clearly lower (very quiet)? (y/n): y
  [PASS] Listening 2: volume lowered

  Volume restored to 128, playing for 1 second...
  >>> Did the volume return to loud? (y/n): y
  [PASS] Listening 3: volume restored

  Pausing for 2 seconds (should be silent)...
  >>> Was it completely silent while paused? (y/n): y
  [PASS] Listening 4: pause is silent

  Resuming for 2 seconds...
  >>> Did playback resume from where it paused? (y/n): y
  [PASS] Listening 5: resume works

  Fade-in test: fadein(2000ms), playing for 3 seconds...
  >>> Did the sound fade in from silence to full volume? (y/n): y
  [PASS] Listening 6: fade-in effect

  Fade-out test: fadeout(2000ms), waiting 3 seconds...
  >>> Did the sound fade out gradually to silence? (y/n): y
  [PASS] Listening 7: fade-out effect

  Seek test: jump to 90s, playing for 2 seconds...
  >>> Did you hear the middle of the song (not the beginning)? (y/n): y
  [PASS] Listening 8: seek positioning

  Listening tests finished

==================================================================
 CICD Test Summary
==================================================================
   Passed : 421
   Failed : 0
   Skipped: 0
==================================================================

You can run the test suite yourself by executing cicd_test.py (included in the package). It will prompt you for an MP3 file path and run all checks:

python cicd_test.py --auto      # automated tests only
python cicd_test.py --listen    # interactive listening tests only
python cicd_test.py --full      # everything (default)

🧪 API Import Validation Test

We provide a comprehensive test script IMPORT-TEST.py that verifies all documented APIs exist and are properly exported. This ensures the documentation stays accurate and users won't encounter missing imports.

Test Coverage

The script tests:

  1. Top-level imports: All functions exported at package level
  2. AudioLibrary methods: All public and documented methods of the AudioLibrary class
  3. Extra APIs: Additional functions mentioned in documentation but not in __all__

Running the Test

# Run the test
python IMPORT-TEST.py

Test Output

Below is the full output of the API import validation test on a Windows system with Python 3.13.4:

============================================================
🧪 Testing All ap_ds API Exports
============================================================

📦 Testing top-level imports:
----------------------------------------

Warning (from warnings module):
  File "C:\Users\dvs.新年快乐\AppData\Local\Programs\Python\Python313\Lib\site-packages\ap_ds\audio_parser.py", line 74
  _check_runtime_mode()
RuntimeWarning: ⚠️ ap_ds: GIL is enabled (multi-core parallelism limited).
  For full performance, upgrade to Python 3.15t:
  https://mirrors.huaweicloud.com/python/3.15.0/python-3.15.0b4t-amd64.zip
  To suppress this warning, set AP_DS_SUPPRESS_WARNINGS=1
AP_DS © - Audio Library By DVS  v4.0.0 | https://apds.top

Warning (from warnings module):
  File "C:\Users\dvs.新年快乐\AppData\Local\Programs\Python\Python313\Lib\site-packages\ap_ds\__init__.py", line 635
  _check_runtime_mode()
RuntimeWarning: ⚠️ ap_ds: GIL is enabled (multi-core parallelism limited).
  For full performance, upgrade to Python 3.15t:
  https://mirrors.huaweicloud.com/python/3.15.0/python-3.15.0b4t-amd64.zip
  To suppress this warning, set AP_DS_SUPPRESS_WARNINGS=1
✅ SDL2 loaded from package directory
ℹ️ _IS_PYTHON_315_PLUS defined now: False (Python 3.13)
🎵 WAV playback mode threshold: 6s (Files >= 6s use music mode, < 6s use sound effect mode)
  ✅ AudioLibrary
  ✅ batch_get_metadata
  ✅ batch_get_duration
  ✅ batch_get_metadata_by_type
  ✅ get_audio_duration
  ✅ get_audio_metadata
  ✅ auto_check_runtime
  ✅ check_runtime_mode
  ✅ show_tech_manual

----------------------------------------
🎯 Testing AudioLibrary methods:
----------------------------------------
  ✅ AudioLibrary.__init__
  ✅ AudioLibrary.play_from_file
  ✅ AudioLibrary.play_from_memory
  ✅ AudioLibrary.new_aid
  ✅ AudioLibrary.play_audio
  ✅ AudioLibrary.pause_audio
  ✅ AudioLibrary.stop_audio
  ✅ AudioLibrary.seek_audio
  ✅ AudioLibrary.set_volume
  ✅ AudioLibrary.get_volume
  ✅ AudioLibrary.fadein_music
  ✅ AudioLibrary.fadein_music_pos
  ✅ AudioLibrary.fadeout_music
  ✅ AudioLibrary.is_music_playing
  ✅ AudioLibrary.is_music_paused
  ✅ AudioLibrary.get_music_fading
  ✅ AudioLibrary.get_audio_duration
  ✅ AudioLibrary.get_audio_metadata
  ✅ AudioLibrary.get_audio_metadata_by_path
  ✅ AudioLibrary.get_audio_metadata_by_aid
  ✅ AudioLibrary.batch_get_metadata
  ✅ AudioLibrary.batch_get_duration
  ✅ AudioLibrary.batch_get_metadata_by_type
  ✅ AudioLibrary.save_dap_to_json
  ✅ AudioLibrary.get_dap_recordings
  ✅ AudioLibrary.clear_dap_recordings
  ✅ AudioLibrary.clear_memory_cache
  ✅ AudioLibrary.cleanup_function
  ✅ AudioLibrary._find_channel_by_aid
  ✅ AudioLibrary._get_file_path_by_aid
  ✅ AudioLibrary._is_music_file
  ✅ AudioLibrary._seek_audio
  ✅ AudioLibrary._get_duration_by_filepath
  ✅ AudioLibrary._get_file_duration

----------------------------------------
🔍 Checking extra APIs (mentioned in docs but maybe not exported):
----------------------------------------
  ✅ is_full_performance (exists!)
  ✅ get_runtime_info (exists!)

============================================================
📊 FINAL SUMMARY
============================================================
🎉 ALL APIs EXIST! Documentation is accurate.
============================================================
✅ Passed: 45
❌ Failed: 0

Result

All 45 APIs tested passed successfully!

Category Count Status
Top-level imports 9 ✅ All pass
AudioLibrary methods 34 ✅ All pass
Extra APIs 2 ✅ All pass
Total 45 ✅ 45/45 passed

Test Script

The IMPORT-TEST.py script is included in the package and can be run at any time to verify API integrity:

# IMPORT-TEST.py
# Full script available in the package
# Tests all documented APIs for existence

This test is automatically run during CI/CD and should always pass before any release.


📦 Version Relationship

Version Type Support Period Use Case
v3.0.0 LTS Long‑Term Support Until March 2031 Production environments
v3.1.x LFV (Latest Feature Version) ~6 months Early adopters (now superseded)
v4.0.0 LFV (Latest Feature Version) ~6 months Early adopters, new features
v4.0.0 LTS (future) Long‑Term Support TBD (after Python 3.15 stable) Next stable production base

📌 Version Upgrade Recommendations

User Type Recommendation
Production environment Continue using v3.0.0 LTS, wait for v4.0.0 LTS
Development / Testing Upgrade to v4.0.0 to experience the new architecture and fixes
Need batch parsing / Python 3.15t support Must upgrade to v4.0.0
Previously affected by v3.1.x metadata bug Must upgrade to v4.0.0
pip install --upgrade ap_ds

🌐 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

⚠️ 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

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


🔄 New: GitHub Repository (Compatibility Mirror)

A Message from the Developer

Recently, Clint Shepherd, a developer from the United States, reached out with a valid concern:

"Why is there no GitHub repository? GitCode is in China and I'm not used to it. apds.top doesn't have collaboration features like issues, pull requests, or forks..."

After careful consideration, we realized he was absolutely right.

While we have no intention of ever returning to GitHub as our primary platform – their customer support was abysmal during our account lockout, and we still hold a grudge – the reality is undeniable:

  • GitHub has an enormous user base
  • The ecosystem is incredibly mature
  • Many developers are simply more comfortable with it
  • Collaboration features (issues, PRs, forks) are standard expectations

So, we've made a pragmatic decision: we created a new GitHub account (dvs-dvsxt) and established a compatibility mirror at:

🔗 https://github.com/dvs-dvsxt/ap_ds

Important: This Is a Compatibility Mirror, NOT the Primary Repository

Aspect Details
Purpose Compatibility mirror for GitHub-centric developers
Primary Home apds.top – Permanent official source
Primary Mirror GitCode – For global access
China Mirror Gitee – For China-based users
GitHub Status ⚠️ Compatibility Mirror – Updated periodically, may lag behind
Best For ✅ GitHub users who want to star, watch, or clone via familiar workflows

What This Means for You

  • If you're a GitHub user: You can now clone, fork, and open issues on GitHub. We'll respond to GitHub issues, but please be patient – response times may be slower than on GitCode or apds.top.

  • If you're a China-based developer: Gitee remains the fastest and most stable option for you. Use it without hesitation.

  • If you want the latest updates: Always check apds.top first. That's where the most current version, changelogs, and announcements live.

  • If you want to contribute: We welcome contributions on any platform – GitCode, Gitee, or GitHub. PRs will be reviewed regardless of where they come from.

Final Repository Strategy (Updated)

Platform Status Purpose
apds.top ✅ Permanent Home Official source, documentation, downloads
GitCode ✅ Primary Mirror Code hosting for global users
GitHub ✅ Compatibility Mirror For GitHub-centric developers (new!)
Gitee ✅ China Mirror Fast access for China‑based users
GitLab (JiHu) ❌ Abandoned No longer maintained

Special thanks to Clint Shepherd for asking the hard question and pushing us to make ap_ds more accessible to the global Python community. Your feedback made this better. 🙏


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 · GitHub Issues · Gitee Issues · Email (completely free)


Official Repositories & Project Sources

✅ Official Primary Repository (First Recommendation): 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 – Globally accessible, modern UI, actively maintained as a public mirror.

✅ Compatibility Mirror (GitHub Users): GitHub – For GitHub-centric developers. May lag behind primary sources. Issues and PRs welcome but may have slower response times.

✅ China Mirror (Fast & Stable): Gitee – Full mirror for China‑based developers, fast access, classic stable UI.

❌ Deprecated & Abandoned:

  • GitHub (dvs-web/ap_ds) – Deprecated due to permanent account lockout, no longer maintained.
  • GitLab (JiHu) – 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 or the public mirrors GitCode, GitHub (dvs-dvsxt/ap_ds), and Gitee.

Developer Personal Homepage & Blog: https://dvsx.top – Blog is currently undergoing maintenance and upgrades. Stay tuned. ap_ds Project Homepage: https://apds.top (Official documentation, releases, and license center).

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


About the Author

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

ap_ds Official Portal

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

👉 The ap_ds project homepage (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) is currently undergoing maintenance and upgrades. Stay tuned.


Let's Get Started!

Installation

pip install ap_ds

Upgrade from an older version:

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 v4.0.0'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
Windows 32‑bit python-3.14.4t-win32.zip
ARM64 python-3.14.4t-arm64.zip

Python 3.15t (Beta) Downloads:

Architecture Download Link
Windows 64‑bit python-3.15.0b4t-amd64.zip
Windows 32‑bit python-3.15.0b4t-win32.zip
ARM64 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:

sudo dnf install python3.14-freethreading

After installation, the interpreter is located at /usr/bin/python3.14t.

Ubuntu/Debian (using deadsnakes PPA):

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:

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)

# 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
  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

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:

# 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

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

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

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

⚠️ CRITICAL: Windows Batch Parsing & BrokenProcessPool

If you are using the batch parsing APIs on Windows, you MUST protect your entry point with if __name__ == "__main__".

Why?

On Windows, ProcessPoolExecutor uses spawn to create new processes. Each subprocess re-imports your main module. Without the entry point guard, this creates an infinite recursion loop that crashes your program with a BrokenProcessPool error.

This is NOT optional. It is MANDATORY.

❌ WRONG (Will crash on Windows):

from ap_ds import batch_get_metadata

# This will crash on Windows with BrokenProcessPool!
results = batch_get_metadata("/music/", max_workers=4)

✅ CORRECT (Always works):

from ap_ds import batch_get_metadata

def main():
    results = batch_get_metadata("/music/", max_workers=4)
    print(f"Parsed {len(results)} files")

if __name__ == "__main__":
    main()

✅ CORRECT (For scripts with configuration):

from ap_ds import batch_get_metadata

def get_config():
    # ... configuration logic ...
    return config

def main():
    config = get_config()
    results = batch_get_metadata(config["audio_dir"], max_workers=4)
    print(f"Parsed {len(results)} files")

if __name__ == "__main__":
    main()

This applies to:

  • ✅ Any script that imports ap_ds and uses batch parsing on Windows
  • ✅ Jupyter notebooks (if running on Windows, wrap the batch call in a function and use if __name__ == "__main__")
  • ✅ Any test scripts (like CI-CD-TEST.py)

Does this apply to Linux/macOS?

No. Linux and macOS use fork by default, which does not have this issue. However, it's still good practice to use the entry point guard for cross-platform compatibility.

DAP Playlist System

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

# 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:

# 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: v4.0.0 Documentation Date: August 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
    • DAP System Methods
    • Resource Management
    • Internal Helper Methods
  2. Top-Level Convenience Functions

  3. AudioParser Module – Metadata API

  4. SDL2 Integration Layer

  5. Constants Reference

  6. Unified Error Handling & Error Codes Reference

  7. Environment Variables Reference

  8. Technical Manual (show_tech_manual())

AudioLibrary Class – Complete API

The AudioLibrary class is the primary interface for audio playback, control, and metadata management. Every method that can fail returns a unified error tuple:

(code: int, message: str, suggestion: str)

On success, methods return their documented success value. On failure, they never raise (except __init__, which is a constructor) — they return the error tuple above. See the Unified Error Handling & Error Codes Reference section for the complete code table.

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.

Note: __init__ is a constructor — it cannot return an error tuple. If SDL2 or mixer initialization fails, it raises RuntimeError (this is the one intentional exception in the library).

Parameters:

Parameter Type Default Description
frequency int 44100 Audio sample rate (Hz). Common values: 44100 (CD quality), 48000 (DVD/video), 22050 (voice).
format int MIX_DEFAULT_FORMAT Audio sample format. Typically AUDIO_S16SYS (16-bit signed, system endianness).
channels int 2 Number of audio channels. 1 = mono, 2 = stereo.
chunksize int 2048 Buffer size in samples. Larger values reduce CPU but increase latency.

Raises (constructor only):

  • RuntimeError: If SDL2 initialization fails (e.g., no audio device available).
  • RuntimeError: If mixer initialization fails (e.g., unsupported format).

Example:

from ap_ds import AudioLibrary

lib = AudioLibrary()                              # default (CD quality, stereo)
lib_voice = AudioLibrary(frequency=22050, channels=1, chunksize=1024)

Playback Methods

play_from_file(file_path: str, loops: int = 0, start_pos: float = 0.0) -> Union[int, Tuple[int, str, str]]

Description:

Loads and plays an audio file directly from disk. .ap-ds-dap files are DAP export records (output format) and are not supported as playback input — passing one returns (1003, msg, suggestion).

Parameters:

Parameter Type Default Description
file_path str / bytes / os.PathLike Required Full path to the audio file. Supported formats: MP3, WAV, FLAC, OGG.
loops int 0 Loops after the first play. 0 = once, -1 = infinite, >0 = count.
start_pos float 0.0 Starting position in seconds. Not supported for sound effects.

Returns:

  • Success: int – A unique Audio ID (AID) identifying this playback instance.
  • Failure: Tuple[int, str, str] – (error_code, error_message, suggestion).

Error Codes:

Code Condition
1001 file_path does not exist, or file_path has an invalid type (None, int, list, dict, tuple, etc.).
1999 loops has an invalid type (must be int).
1003 Audio failed to load (e.g., a .ap-ds-dap file, corrupted file, or unsupported format).
1004 Playback failed (e.g., no available channel or audio device issue).

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:

aid = lib.play_from_file("song.mp3")                  # play once
aid = lib.play_from_file("beep.wav", loops=5)         # loop 5 times
aid = lib.play_from_file("podcast.mp3", start_pos=30.0)
aid = lib.play_from_file("ambient.ogg", loops=-1)     # infinite loop

# Invalid inputs return error tuples (no exception):
result = lib.play_from_file(None)          # (1001, 'Invalid file path type: NoneType', ...)
result = lib.play_from_file("x.mp3", loops="2")  # (1999, 'Invalid loops type: str...', ...)

Internal Workflow:

  1. Validates file_path and loops types.
  2. Increments _aid_counter to generate a new AID.
  3. Calls _add_to_dap_recordings() to record metadata (if available).
  4. Determines playback mode via _is_music_file().
  5. For music files: Mix_LoadMUS() → Mix_PlayMusic() → store in _music_cache, channel = -1.
  6. For sound effects: Mix_LoadWAV() → Mix_PlayChannel(-1, audio, loops) → store in _audio_cache.
  7. Stores playback info in _channel_info[channel].
  8. If start_pos > 0, calls _seek_audio().

play_from_memory(file_path: str, loops: int = 0, start_pos: float = 0.0) -> Union[int, Tuple[int, str, str]]

Description:

Plays an audio file that has been preloaded into memory via new_aid(). Faster than play_from_file() for repeated plays since the file is already cached.

Parameters: Same as play_from_file().

Returns:

  • Success: int (AID).
  • Failure: Tuple[int, str, str] – (error_code, error_message, suggestion).

Error Codes:

Code Condition
1013 file_path is not loaded in memory, or file_path has an invalid type (None, list, dict, etc.).
1999 loops has an invalid type (must be int).
1004 Playback from cache failed.

Example:

lib.new_aid("gunshot.wav")
lib.new_aid("explosion.wav")
aid = lib.play_from_memory("gunshot.wav")   # instant, no disk I/O

Internal Workflow:

  1. Validates file_path and loops types.
  2. Generates a new AID and records to DAP.
  3. Checks _music_cache / _audio_cache.
  4. Plays from the cached object and records info in _channel_info.

new_aid(file_path: str) -> Union[int, Tuple[int, str, str]]

Description:

Preloads an audio file into memory without playing it. Useful for caching sounds or tracks that will be played multiple times.

Parameters:

Parameter Type Description
file_path str / bytes / os.PathLike Path to the audio file to cache.

Returns:

  • Success: int (AID) for the cached file.
  • Failure: Tuple[int, str, str] – (error_code, error_message, suggestion).

Error Codes:

Code Condition
1001 file_path does not exist or has an invalid type (None, float, list, etc.).
1003 Audio failed to load.

Example:

sounds = {
    'hit':  lib.new_aid("hit.wav"),
    'jump': lib.new_aid("jump.wav"),
    'coin': lib.new_aid("coin.wav"),
}
lib.play_from_memory(sounds['hit'])

Internal Workflow:

  1. Validates file_path type.
  2. Increments AID counter and records to DAP.
  3. Determines music vs sound effect via _is_music_file().
  4. Loads via Mix_LoadMUS() / Mix_LoadWAV() and stores in the appropriate cache.
  5. Maps AID to file path in _aid_to_filepath.

Control Methods

play_audio(aid: int) -> Tuple[int, str, str]

Description:

Resumes a paused audio instance identified by aid.

Parameters:

Parameter Type Description
aid int The Audio ID returned by a play/load method.

Returns:

  • Success: (0, "", "") – AP_DS_SUCCESS.
  • Failure: Tuple[int, str, str] – (error_code, error_message, suggestion).

Error Codes:

Code Condition
1002 aid is invalid (no matching active playback instance).

Example:

lib.pause_audio(aid)
lib.play_audio(aid)      # resume
result = lib.play_audio(99999)   # (1002, 'Invalid AID: 99999', ...)

pause_audio(aid: int) -> Tuple[int, str, str]

Description:

Pauses the audio instance identified by aid. Playback can be resumed with play_audio().

Parameters:

Parameter Type Description
aid int The Audio ID.

Returns:

  • Success: (0, "", "").
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1002 aid is invalid.

Example:

lib.pause_audio(aid)
result = lib.pause_audio(99999)   # (1002, 'Invalid AID: 99999', ...)

stop_audio(aid: int) -> Union[float, Tuple[int, str, str]]

Description:

Stops playback and returns the played duration in seconds.

Parameters:

Parameter Type Description
aid int The Audio ID.

Returns:

  • Success: float – Played duration in seconds.
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1002 aid is invalid.

Example:

played = lib.stop_audio(aid)          # e.g. 3.42
result = lib.stop_audio(99999)        # (1002, 'Invalid AID: 99999', ...)

seek_audio(aid: int, position: float) -> Tuple[int, str, str]

Description:

Seeks the audio instance to a specified position (seconds). Only supported for music-mode files (MP3, OGG, FLAC, long WAV).

Parameters:

Parameter Type Description
aid int The Audio ID.
position int / float Position in seconds.

Returns:

  • Success: (0, "", "").
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1002 aid is invalid.
1999 position has an invalid type (None, str, etc.).

Example:

lib.seek_audio(aid, 30.5)
result = lib.seek_audio(aid, None)    # (1999, 'Invalid position type: NoneType...', ...)

Volume Methods

set_volume(aid: int, volume: int) -> Tuple[int, str, str]

Description:

Sets the volume for the audio instance. Volume range is 0–128.

Parameters:

Parameter Type Description
aid int The Audio ID.
volume int Volume value 0–128.

Returns:

  • Success: (0, "", "").
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1002 aid is invalid.
1015 volume is not an int, or is outside 0–128.
1004 Mixer failed to apply the volume.

Example:

lib.set_volume(aid, 64)
result = lib.set_volume(aid, "loud")   # (1015, 'Invalid volume type: str...', ...)
result = lib.set_volume(aid, 200)      # (1015, 'Invalid volume: 200 (must be 0-128)', ...)

get_volume(aid: int) -> Union[int, Tuple[int, str, str]]

Description:

Returns the current volume (0–128) of the audio instance.

Parameters:

Parameter Type Description
aid int The Audio ID.

Returns:

  • Success: int – Current volume (0–128).
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1002 aid is invalid.

Example:

v = lib.get_volume(aid)      # e.g. 64
result = lib.get_volume(99999)   # (1002, 'Invalid AID: 99999', ...)

Fade & Transition Methods

fadein_music(aid: int, loops: int = -1, ms: int = 0) -> Tuple[int, str, str]

Description:

Fades in music from silence to full volume over ms milliseconds.

Parameters:

Parameter Type Default Description
aid int — AID of a music file.
loops int -1 -1 = infinite, 0 = once, >0 = count.
ms int 0 Fade-in duration in milliseconds.

Returns:

  • Success: (0, "", "").
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1002 aid is invalid or not a music file.
1999 ms / loops has an invalid type (must be int).
1003 Music failed to load.
1004 SDL_mixer fade failure.

Example:

lib.fadein_music(aid, ms=2000)          # 2-second fade in
result = lib.fadein_music(aid, ms=None) # (1999, 'Invalid fade parameters...', ...)

fadein_music_pos(aid: int, loops: int = -1, ms: int = 0, position: float = 0.0) -> Tuple[int, str, str]

Description:

Fades in music from a specified position.

Parameters:

Parameter Type Default Description
aid int — AID of a music file.
loops int -1 Loop count.
ms int 0 Fade-in duration in milliseconds.
position int / float 0.0 Start position in seconds.

Returns:

  • Success: (0, "", "").
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1002 aid is invalid or not a music file.
1999 ms / loops / position has an invalid type.
1012 Mix_FadeInMusicPos is not supported by this SDL_mixer version.
1003 Music failed to load.
1004 SDL_mixer fade failure.

Example:

lib.fadein_music_pos(aid, ms=2000, position=30.0)
result = lib.fadein_music_pos(aid, ms=100, position=None)  # (1999, ...)

fadeout_music(ms: int = 0) -> Tuple[int, str, str]

Description:

Fades out the currently playing music over ms milliseconds.

Parameters:

Parameter Type Default Description
ms int 0 Fade-out duration in milliseconds.

Returns:

  • Success: (0, "", "").
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1004 No music is playing, or the fade-out failed.

Example:

lib.fadeout_music(2000)   # 2-second fade out

is_music_playing() -> bool

Description:

Returns whether music is currently playing.

Returns: bool – True if playing, False otherwise.


is_music_paused() -> bool

Description:

Returns whether music is currently paused.

Returns: bool – True if paused, False otherwise.


get_music_fading() -> int

Description:

Returns the current fade state.

Returns: int:

  • 0 (MUS_NO_FADING): No fade in progress.
  • 1 (MUS_FADING_IN): Fading in.
  • 2 (MUS_FADING_OUT): Fading out.

Metadata Methods

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

Description:

Returns the duration of an audio file in seconds. Accepts either a file path (str) or an AID (int).

Parameters:

Parameter Type Default Description
source str or int — File path or AID.
is_file bool False If True, treats source as a file path.

Returns:

  • Success: int – Duration in seconds (floor).
  • Failure: Tuple[int, str, str] – (error_code, error_message, suggestion).

Error Codes:

Code Condition
1001 File does not exist.
1002 Invalid AID.
1011 Metadata parse failure.
1999 Unknown error (e.g., unsupported file).

Example:

d = lib.get_audio_duration("song.mp3", is_file=True)   # e.g. 240
d = lib.get_audio_duration(aid)                        # by AID
result = lib.get_audio_duration(99999)                 # (1002, 'Invalid AID: 99999', ...)

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

Description:

Returns complete metadata for an audio file. Accepts a file path (str) or an AID (int).

Parameters:

Parameter Type Default Description
source str or int — File path or AID.
is_file bool False If True, treats source as a file path.

Returns:

  • Success: Dict with keys: path, format, duration, length, sample_rate, channels, bitrate.
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1001 File does not exist.
1002 Invalid AID.
1011 Metadata parse failure.
1014 source has an invalid type (not str / int).

Example:

meta = lib.get_audio_metadata("song.mp3", is_file=True)
print(meta['duration'], meta['sample_rate'], meta['channels'])
result = lib.get_audio_metadata(1.5)   # (1014, 'Invalid source type...', ...)

get_audio_metadata_by_path(file_path: str) -> Union[Dict, Tuple[int, str, str]]

Description:

Returns complete metadata for an audio file by path.

Parameters:

Parameter Type Description
file_path str Path to the audio file.

Returns:

  • Success: Dict (same keys as above).
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1001 File does not exist.
1011 Metadata parse failure.
1999 Unknown error.

get_audio_metadata_by_aid(aid: int) -> Union[Dict, Tuple[int, str, str]]

Description:

Returns complete metadata for an audio file by AID.

Parameters:

Parameter Type Description
aid int The Audio ID.

Returns:

  • Success: Dict.
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1002 Invalid AID.
1011 Metadata parse failure.
1999 Unknown error.

simple_mp3_duration_estimation(filename: str) -> float

Description:

Estimates MP3 duration from file size and a typical bitrate. Fallback when frame parsing fails.

Returns: float – Estimated duration in seconds, 0.0 on error.


Batch Parsing Methods

Windows note: When using batch APIs, protect your entry point with if __name__ == "__main__": (see the CRITICAL section at the top of this document).

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 ProcessPoolExecutor. Accepts a list of file paths or a directory path (recursively scanned for supported formats).

Parameters:

Parameter Type Default Description
file_paths List[str] or str — File list, or a directory path.
max_workers int None Worker count. Defaults to CPU count. Must be a positive int (within platform limits).
show_progress bool False Print progress to stdout.

Returns:

  • Success: List[Dict] – Metadata dicts for successfully parsed files (failed ones omitted).
  • Failure: Tuple[int, str, str] – returned when max_workers is invalid.

Error Codes:

Code Condition
1999 max_workers is invalid (0, negative, non-int, or above the platform limit).

Example:

results = batch_get_metadata("/music/", max_workers=4, show_progress=True)
result = batch_get_metadata(["a.mp3"], max_workers=0)  # (1999, 'Invalid max_workers...', ...)

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

Description:

Returns durations for multiple files in parallel.

Returns:

  • Success: Dict[str, int] – {path: duration_seconds}.
  • Failure: Tuple[int, str, str] – invalid max_workers.

Error Codes:

Code Condition
1999 max_workers is invalid.

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

Description:

Parses multiple files but only returns results matching a specific format (e.g., "mp3", "flac").

Returns:

  • Success: List[Dict] – Metadata for matching files.
  • Failure: Tuple[int, str, str] – invalid max_workers.

Error Codes:

Code Condition
1999 max_workers is invalid.

DAP System Methods

save_dap_to_json(save_path: str) -> Tuple[int, str, str]

Description:

Saves DAP recordings to a .ap-ds-dap JSON file.

Parameters:

Parameter Type Description
save_path str Output path. Must end with .ap-ds-dap.

Returns:

  • Success: (0, "", "").
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1009 Extension is not .ap-ds-dap.
1010 Write failure (bad path, permissions, disk full).

Example:

result = lib.save_dap_to_json("history.ap-ds-dap")   # (0, '', '')
result = lib.save_dap_to_json("out.json")            # (1009, ...)

get_dap_recordings() -> List[Dict]

Description:

Returns the current DAP recordings (a copy).

Returns: List[Dict] – each record has keys path, duration, bitrate, channels.


clear_dap_recordings() -> None

Description:

Clears all DAP recordings from memory.

Returns: None.


_add_to_dap_recordings(file_path: str) -> None

Description (internal):

Records an audio file's metadata into the DAP list with O(1) set-based deduplication (O(n) linear fallback). Silently skips unparseable files.

Returns: None.


Resource Management

clear_memory_cache() -> None

Description:

Frees all cached Mix_Chunk and Mix_Music objects and clears the caches.

Returns: None.


cleanup_function() -> None

Description:

Cleans up all resources: clears caches, closes the mixer, and quits SDL. Registered with atexit automatically.

Returns: None.


Internal Helper Methods

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

Description (internal):

Finds the channel number associated with an AID.

Returns: Optional[int] – channel number, or None if not found.


_get_file_path_by_aid(aid: int) -> Union[str, Tuple[int, str, str]]

Description (internal):

Returns the file path associated with an AID.

Returns:

  • Success: str – file path.
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1002 Invalid AID.

_is_music_file(file_path: str) -> bool

Description (internal):

Determines whether a file should use music mode or sound-effect mode.

Returns: bool – True for music mode (MP3/OGG/FLAC, or WAV ≥ threshold), False for sound-effect mode.


_seek_audio(channel: int, position: float) -> Tuple[int, str, str]

Description (internal):

Performs the actual seek operation on a channel.

Returns:

  • Success: (0, "", "").
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1002 Channel not in tracking.
1003 Music failed to load for seeking.
1004 Playback failed after reload.
1013 Sound effect not in cache.

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

Description (internal):

Gets duration for a file path using the metadata parser, with a file-size estimation fallback.

Returns:

  • Success: int – duration in seconds.
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1001 File not found.
1011 Metadata parse failure (estimation also failed).
1999 Unknown error.

_get_file_duration(file_path: str) -> float

Description (internal):

Returns the file duration as float; returns 0.0 on failure.

Returns: float.


_get_sample_rate(source: Union[str, int]) -> int

Description (internal):

Returns the sample rate of an audio source.

Returns: int – sample rate in Hz, or 44100 fallback.


_get_channels(source: Union[str, int]) -> int

Description (internal):

Returns the channel count of an audio source.

Returns: int – channels, or 2 fallback.


_get_aid_for_audio(file_path: str) -> Union[int, Tuple[int, str, str]]

Description (internal):

Finds the AID of a sound-effect file by path.

Returns:

  • Success: int (AID).
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1002 No matching sound-effect AID.

_get_aid_for_music(file_path: str) -> Union[int, Tuple[int, str, str]]

Description (internal):

Finds the AID of a music file by path.

Returns:

  • Success: int (AID).
  • Failure: Tuple[int, str, str].

Error Codes:

Code Condition
1002 No matching music AID.

_get_playing_duration(aid: int) -> float

Description (internal):

Returns the total duration of the playing audio.

Returns: float.


Top-Level Convenience Functions

The following functions are exported at the package top level (from ap_ds import ...).

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. Accepts a list of file paths or a directory (recursively scanned).

Returns:

  • Success: List[Dict] – metadata for parsed files (failed ones omitted).
  • Failure: Tuple[int, str, str] – invalid max_workers → (1999, ...).

Example:

from ap_ds import batch_get_metadata

results = batch_get_metadata("/music/", max_workers=4)
result = batch_get_metadata(["a.mp3"], max_workers=0)   # (1999, 'Invalid max_workers...', ...)

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

Description:

Returns {path: duration_seconds} for multiple files in parallel.

Returns:

  • Success: Dict[str, int].
  • Failure: Tuple[int, str, str] – invalid max_workers.

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

Description:

Parses multiple files and returns only those matching file_type (e.g., "mp3").

Returns:

  • Success: List[Dict].
  • Failure: Tuple[int, str, str] – invalid max_workers.

get_audio_duration(file_path: str) -> int

Description:

Returns the duration (seconds) of a single audio file.

Returns: int – duration in seconds, 0 on failure.


get_audio_metadata(file_path: str) -> Optional[Dict]

Description:

Returns complete metadata for a single audio file.

Returns: Optional[Dict] – metadata dict, or None on failure.


auto_check_runtime() -> Optional[Dict]

Description:

Runs the runtime self-check. By default (with AP_DS_SKIP_AUTO_CHECK=1) it is skipped and returns None. With the check enabled it returns a dict of runtime information.

Returns: Optional[Dict] – None (skipped) or a runtime-info dict.


check_runtime_mode() -> bool

Description:

Checks whether the GIL is enabled.

Returns: bool – True if GIL is enabled, False if free-threading.


show_tech_manual() -> None

Description:

Prints the complete built-in technical manual to stdout.

Returns: None.

Example:

from ap_ds import show_tech_manual
show_tech_manual()

is_full_performance() -> bool

Description:

Returns whether the library is running in full performance mode (free-threading + profiling available).

Returns: bool.


get_runtime_info() -> Dict

Description:

Returns the runtime information dictionary.

Returns: Dict – with keys library_name, library_version, library_install_path, library_website, library_author, python_version, gil_enabled, has_profiling, is_full_performance, cpu_count, platform. Returns {} if the self-check is skipped.


AudioParser Module – Metadata API

The audio_parser module provides pure-Python metadata parsers with zero external dependencies.

Format-Specific Parser Classes

Class Format Accuracy Parsing Method
WAVFile WAV 100% RIFF chunk structure
FLACFile FLAC 100% STREAMINFO metadata block
MP3File MP3 >98% Frame-by-frame sync-word scanning
AACFile AAC (ADTS) >99% ADTS frame parsing
OGGFile OGG Vorbis 99.99% Granule position + Vorbis header

All parser classes inherit from FileType and expose the properties length, sample_rate, channels, and bitrate.

open_audio(filename: str) -> FileType

Description:

Factory function that returns the appropriate parser instance for a file.

Raises:

  • ValueError: If the file format is unsupported (this is the documented behavior of the factory).

Example:

from ap_ds.audio_parser import open_audio
parser = open_audio("song.mp3")       # -> MP3File
print(parser.length, parser.sample_rate, parser.channels, parser.bitrate)

StreamInfo Class

Description:

Container for parsed stream metadata.

Attributes:

Attribute Type Description
length float Duration in seconds.
sample_rate int Sample rate in Hz.
channels int Channel count (1 = mono, 2 = stereo).
bitrate int Bitrate in bits per second.

get_audio_duration(file_path: str) -> int

Description:

Returns the duration (seconds) of a single audio file.

Returns: int – duration, 0 on failure.


get_audio_metadata(file_path: str) -> Optional[Dict]

Description:

Returns complete metadata for a single audio file.

Returns: Optional[Dict] – dict with keys path, format, duration, length, sample_rate, channels, bitrate, or None on failure.


SDL2 Integration Layer

The _sdl2 module provides the cross-platform SDL2 loader, constants, structures, and ctypes bindings. It is an internal module; users interact with it indirectly through AudioLibrary.

Global SDL2 Functions

Function Description
SDL_Init(flags) Initializes SDL subsystems. Returns 0 on success.
SDL_InitSubSystem(flags) Initializes specific subsystems.
SDL_Quit() Shuts down SDL.
SDL_QuitSubSystem(flags) Shuts down specific subsystems.
SDL_WasInit(flags) Returns which subsystems are initialized.
SDL_GetError() Returns the last SDL error message.
SDL_RWFromFile(file, mode) Opens a file as an SDL RWops handle.
SDL_Delay(ms) Pauses the calling thread for ms milliseconds.

Global SDL2_mixer Functions

Function Description
Mix_OpenAudio(freq, format, channels, chunksize) Opens the audio mixer. Returns 0 on success.
Mix_CloseAudio() Closes the mixer.
Mix_LoadWAV(file) Loads a WAV sound effect into a Mix_Chunk.
Mix_LoadMUS(file) Loads a music file into a Mix_Music.
Mix_FreeChunk(chunk) Frees a sound-effect chunk.
Mix_FreeMusic(music) Frees a music object.
Mix_PlayChannel(channel, chunk, loops) Plays a chunk on a channel.
Mix_PlayMusic(music, loops) Plays music.
Mix_Pause(channel) / Mix_PauseMusic() Pauses a channel / music.
Mix_Resume(channel) / Mix_ResumeMusic() Resumes a channel / music.
Mix_HaltChannel(channel) / Mix_HaltMusic() Stops a channel / music.
Mix_SetMusicPosition(position) Seeks music to a position (seconds).
Mix_MusicDuration(music) Returns music duration in seconds.
Mix_Volume(channel, volume) Sets/gets channel volume (0–128, -1 = get).
Mix_VolumeMusic(volume) Sets/gets music volume (0–128, -1 = get).
Mix_AllocateChannels(num) Allocates mixer channels.
Mix_GetMusicType(music) Returns the music type.
Mix_FadingMusic() Returns the current music fade state.
Mix_FadeInMusic(music, loops, ms) Fades music in.
Mix_FadeOutMusic(ms) Fades music out.
Mix_FadeInChannel(channel, chunk, loops, ms) Fades a channel in.
Mix_FadeOutChannel(channel, ms) Fades a channel out.
Mix_Playing(channel) / Mix_PlayingMusic() Query playing state.
Mix_Paused(channel) / Mix_PausedMusic() Query paused state.
Mix_SetPanning(channel, left, right) Sets channel panning.
Mix_SetDistance(channel, distance) Sets channel distance.
Mix_SetPosition(channel, angle, distance) Sets channel position.
Mix_SetReverseStereo(channel, flip) Reverses stereo channels.
Mix_FadeInMusicPos(music, loops, ms, position) Fades music in from a position.

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 subsystem.
SDL_INIT_GAMECONTROLLER 0x00002000 Game controller subsystem.
SDL_INIT_EVENTS 0x00004000 Events subsystem.
SDL_INIT_EVERYTHING 0x00007231 All subsystems combined.
SDL_TRUE / SDL_FALSE 1 / 0 Boolean constants.

Audio Formats

Constant Value Description
AUDIO_U8 0x0008 Unsigned 8-bit.
AUDIO_S8 0x8008 Signed 8-bit.
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_S32LSB 0x8020 Signed 32-bit little-endian.
AUDIO_S32MSB 0x9020 Signed 32-bit big-endian.
AUDIO_F32LSB 0x8120 Float 32-bit little-endian.
AUDIO_F32MSB 0x9120 Float 32-bit big-endian.
AUDIO_U16SYS / AUDIO_S16SYS — System-endian 16-bit aliases.
MIX_DEFAULT_FORMAT AUDIO_S16SYS Default mixer format.

Mixer Initialization Flags

Constant Value Description
MIX_INIT_FLAC 0x00000001 FLAC support.
MIX_INIT_MOD 0x00000002 MOD support.
MIX_INIT_MP3 0x00000008 MP3 support.
MIX_INIT_OGG 0x00000010 OGG 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.
MUS_CMD 1 Command-based.
MUS_WAV 2 WAV.
MUS_MOD 3 MOD.
MUS_MID 4 MIDI.
MUS_OGG 5 OGG.
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_IN 1 Fading in.
MUS_FADING_OUT 2 Fading out.

Other Constants

Constant Value Description
MIX_CHANNEL_POST -2 Post-mix channel.
MIX_DEFAULT_CHANNELS 2 Default channel count.

Unified Error Handling & Error Codes Reference

Overview

Starting from v4.0.0, ap_ds uses a unified error handling system. All methods that can fail return a consistent tuple format:

(error_code: int, error_message: str, suggestion: str)

Success: (AP_DS_SUCCESS, "", "") — This indicates the operation completed successfully.

Failure: (error_code, error_message, suggestion) — The error_code identifies the specific issue, error_message provides a human-readable description, and suggestion offers actionable advice for resolving the problem.

Why This Change?

In previous versions (v3.x), error handling was inconsistent:

  • Some methods raised exceptions (FileNotFoundError, ValueError, RuntimeError)
  • Some methods returned None
  • Some methods returned False
  • Some methods returned 0 or 0.0
  • Users had to remember different error handling patterns for different methods

This inconsistency made error handling confusing and error-prone. With v4.0.0, we've standardized everything.

Complete Error Code Reference

Code Constant Meaning Suggestion
0 AP_DS_SUCCESS Operation completed successfully No action needed
1001 AP_DS_ERR_FILE_NOT_FOUND The specified file does not exist Verify the file path exists and is accessible
1002 AP_DS_ERR_INVALID_AID The Audio ID is invalid or expired Check that the AID is valid and the audio is loaded
1003 AP_DS_ERR_AUDIO_LOAD_FAILED Failed to load audio file Check file format and integrity; file may be corrupted
1004 AP_DS_ERR_PLAYBACK_FAILED Audio playback failed Check audio device and file format
1005 AP_DS_ERR_SDL_INIT_FAILED SDL2 initialization failed Check SDL2 installation and audio drivers
1006 AP_DS_ERR_MIXER_INIT_FAILED SDL2_mixer initialization failed Check audio device and available formats
1007 AP_DS_ERR_UNSUPPORTED_FORMAT Audio format is not supported Use one of: MP3, WAV, FLAC, OGG (AAC for metadata only)
1008 AP_DS_ERR_NOT_MUSIC_FILE Operation only works on music files This operation (e.g., seeking) is not supported for sound effects
1009 AP_DS_ERR_DAP_INVALID_EXT Invalid file extension for DAP Use .ap-ds-dap extension when saving DAP recordings
1010 AP_DS_ERR_DAP_SAVE_FAILED Failed to save DAP recordings Check write permissions and disk space
1011 AP_DS_ERR_METADATA_PARSE_FAILED Failed to parse audio metadata File may be corrupted or use an unsupported variant
1012 AP_DS_ERR_FADE_NOT_SUPPORTED Fade operation not supported by SDL_mixer Update SDL_mixer or use alternative fade methods
1013 AP_DS_ERR_AUDIO_NOT_LOADED Audio file is not loaded in memory Call new_aid() first to load the file into cache
1014 AP_DS_ERR_INVALID_SOURCE Invalid source type for metadata query Use file path (str) or AID (int) as source
1015 AP_DS_ERR_INVALID_VOLUME Volume value is out of range Volume must be between 0 and 128 inclusive
1016 AP_DS_ERR_SEEK_NOT_SUPPORTED Seeking is not supported for this audio Sound effects (short WAVs) do not support seeking
1999 AP_DS_ERR_UNKNOWN An unexpected error occurred Check file integrity and try again; report if persistent

Argument Validation Error Codes

In addition to the operation-specific codes above, ap_ds performs argument type validation at every public entry point. Passing an invalid type returns the following codes:

Parameter Methods Valid Type Error Code
file_path play_from_file, new_aid str / bytes / os.PathLike 1001
file_path play_from_memory str / bytes / os.PathLike 1013
loops play_from_file, play_from_memory int 1999
position seek_audio int / float 1999
volume set_volume int 1015
ms / loops fadein_music, fadein_music_pos int 1999
position fadein_music_pos int / float 1999
max_workers batch_get_metadata int (1 .. platform max) 1999

How to Handle Errors

Example 1: Checking Return Values

from ap_ds import AudioLibrary

lib = AudioLibrary()

# Method that returns int on success, tuple on failure
result = lib.play_from_file("song.mp3")

if isinstance(result, tuple):
    code, msg, suggestion = result
    print(f"Error {code}: {msg}")
    print(f"Suggestion: {suggestion}")
else:
    aid = result
    print(f"Playing with AID: {aid}")

Example 2: Using Helper Functions

def is_success(result):
    """Helper to check if a result indicates success."""
    if isinstance(result, tuple):
        return result[0] == 0
    return True  # Non-tuple results are success values

def get_error(result):
    """Helper to extract error details."""
    if isinstance(result, tuple) and result[0] != 0:
        return {
            'code': result[0],
            'message': result[1],
            'suggestion': result[2]
        }
    return None

Example 3: Graceful Degradation

# Try to get duration, fall back gracefully
result = lib.get_audio_duration("problematic_file.mp3", is_file=True)

if isinstance(result, tuple):
    print(f"Using fallback duration: 0 seconds")
    print(f"Error: {result[1]}")
else:
    print(f"Duration: {result} seconds")

Method Return Type Summary

Every public method follows the unified error-tuple convention. The table below lists every method, its success return value, and the complete set of error codes it may return.

Method Success Return Failure Return Possible Error Codes
play_from_file() int (AID) (code, msg, suggestion) 1001, 1003, 1004, 1999
play_from_memory() int (AID) (code, msg, suggestion) 1004, 1013, 1999
new_aid() int (AID) (code, msg, suggestion) 1001, 1003, 1999
play_audio() (0, "", "") (code, msg, suggestion) 1002
pause_audio() (0, "", "") (code, msg, suggestion) 1002
stop_audio() float (duration) (code, msg, suggestion) 1002
seek_audio() (0, "", "") (code, msg, suggestion) 1002, 1999
set_volume() (0, "", "") (code, msg, suggestion) 1002, 1004, 1015
get_volume() int (volume) (code, msg, suggestion) 1002
get_audio_duration() int (seconds) (code, msg, suggestion) 1001, 1002, 1011, 1999
get_audio_metadata() Dict (code, msg, suggestion) 1001, 1002, 1011, 1014
get_audio_metadata_by_path() Dict (code, msg, suggestion) 1001, 1011, 1999
get_audio_metadata_by_aid() Dict (code, msg, suggestion) 1002, 1011, 1999
save_dap_to_json() (0, "", "") (code, msg, suggestion) 1009, 1010
fadein_music() (0, "", "") (code, msg, suggestion) 1002, 1003, 1004, 1999
fadein_music_pos() (0, "", "") (code, msg, suggestion) 1002, 1003, 1004, 1012, 1999
fadeout_music() (0, "", "") (code, msg, suggestion) 1004
batch_get_metadata() List[Dict] (code, msg, suggestion) 1999
batch_get_duration() Dict[str, int] (code, msg, suggestion) 1999
batch_get_metadata_by_type() List[Dict] (code, msg, suggestion) 1999
Delay() None — (low-level SDL delay, no error tuple) —
_get_file_path_by_aid() str (path) (code, msg, suggestion) 1002
_get_aid_for_audio() int (AID) (code, msg, suggestion) 1002
_get_aid_for_music() int (AID) (code, msg, suggestion) 1002

Error code legend (the full meaning of every code is documented in the Error Codes Reference section):

Code Meaning
0 Success
1001 File not found / invalid file path type
1002 Invalid AID
1003 Audio load failed
1004 Playback failed
1009 DAP invalid extension
1010 DAP save failed
1011 Metadata parse failed
1012 Fade not supported
1013 Audio not loaded / invalid path type for memory play
1014 Invalid source type
1015 Invalid volume
1999 Unknown error / invalid argument type (loops, position, ms, max_workers)

Detailed error scenarios per method:

Method Scenario Error Code
play_from_file() File does not exist 1001
play_from_file() file_path is None / int / list / dict 1001
play_from_file() loops is a string / None 1999
play_from_file() Music file load failed (e.g. DAP file) 1003
play_from_file() SDL mixer failed to start playback 1004
play_from_memory() file_path invalid type 1013
play_from_memory() Audio not loaded in cache 1013
play_from_memory() loops invalid type 1999
play_from_memory() Cache playback failed 1004
new_aid() File does not exist 1001
new_aid() file_path invalid type 1001
new_aid() Load failed 1003
seek_audio() Invalid AID 1002
seek_audio() position is None / string 1999
set_volume() Invalid AID 1002
set_volume() volume is not an int 1015
set_volume() volume out of 0-128 range 1015
set_volume() Mixer failed to apply 1004
fadein_music() Invalid AID / not a music file 1002
fadein_music() ms / loops invalid type 1999
fadein_music() Music load failed 1003
fadein_music() SDL mixer fade failure 1004
fadein_music_pos() Mix_FadeInMusicPos unavailable 1012
fadein_music_pos() position invalid type 1999
save_dap_to_json() Extension is not .ap-ds-dap 1009
save_dap_to_json() Write failure (bad path, permissions) 1010
get_audio_duration() Invalid AID 1002
get_audio_duration() File not found 1001
get_audio_duration() Metadata parse failure 1011
get_audio_metadata() Source is float / other non-str-int 1014
batch_get_metadata() max_workers is 0 / -1 / 1.5 / "2" / >61 1999

Environment Variables Reference

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:

# 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:

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.

Usage:

# 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

3. AP_DS_SUPPRESS_WARNINGS

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:

export AP_DS_SUPPRESS_WARNINGS=1

4. AP_DS_SHOW_CONGRATS

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:

export AP_DS_SHOW_CONGRATS=0

5. AP_DS_SKIP_AUTO_CHECK

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

Default: 1 (self-check skipped)

Behavior:

  • When set to 0, executes the runtime self-check on import.
  • Useful for debugging or when you want to see environment diagnostics.

Usage:

# Enable self-check
export AP_DS_SKIP_AUTO_CHECK=0

# Disable self-check (default)
export AP_DS_SKIP_AUTO_CHECK=1

Technical Manual (show_tech_manual())

AP_DS 4.0.0 includes a built-in technical manual that can be displayed by calling show_tech_manual(). This manual contains:

  • Overview of the library
  • Supported audio formats
  • Core components (AudioLibrary, Metadata Parsers, SDL2 Loader)
  • DAP System documentation
  • WAV Smart Mode documentation
  • Environment variables reference
  • Performance optimization tips
  • Cross-platform notes
  • Troubleshooting guide
  • API reference
  • Version history
  • Contributing & support information

To view the manual:

from ap_ds import show_tech_manual
show_tech_manual()

The manual is printed to stdout and contains comprehensive technical information about the library.

The manual (generated by show_tech_manual()) consists of 12 comprehensive sections, each covering a distinct aspect of the library:

1. Overview

  • Library goals and positioning: a lightweight (2.5 MB), zero-dependency Python audio library built on SDL2 / SDL2_mixer.
  • Architecture summary: player.py (AudioLibrary) + _sdl2.py (SDL2 loader, constants, bindings) + audio_parser.py (pure-Python metadata) + __init__.py (package entry).
  • Feature highlights: batch parsing, DAP recording, smart WAV mode, fade controls, free-threading support.

2. Supported Audio Formats

  • MP3: frame-by-frame scanning, >98% accuracy.
  • WAV: RIFF chunk parsing, 100% accuracy.
  • FLAC: STREAMINFO metadata block, 100% accuracy.
  • OGG Vorbis: granule position, 99.99% accuracy.
  • AAC (ADTS): frame parsing, >99% accuracy (metadata only; not playable).

3. Core Components

  • AudioLibrary (player.py): playback (play_from_file, play_from_memory, new_aid), control (pause, play, stop, seek), volume, fade, and the DAP system.
  • Metadata Parsers (audio_parser.py): get_audio_duration, get_audio_metadata, and the batch APIs (batch_get_metadata, batch_get_duration, batch_get_metadata_by_type).
  • SDL2 Loader (_sdl2.py): cross-platform library discovery, automatic download with SHA-256 verification, constants, structures, and ctypes bindings.

4. DAP (Dvs Audio Playlist) System

  • Automatically records every file that is played or loaded through AudioLibrary.
  • O(1) set-based deduplication with an O(n) linear-scan fallback.
  • Persists records to .ap-ds-dap JSON files via save_dap_to_json().
  • Stores metadata only (path, duration, bitrate, channels) — no audio data.

5. WAV Smart Mode

  • Files shorter than AP_DS_WAV_THRESHOLD (default 6 seconds) → sound-effect mode (Mix_PlayChannel, in-memory Mix_Chunk).
  • Files at or above the threshold → music mode (Mix_PlayMusic, streaming, full seek / fade support).
  • The threshold is configurable via the AP_DS_WAV_THRESHOLD environment variable.

6. Environment Variables

  • AP_DS_WAV_THRESHOLD — WAV mode-switching threshold (default 6, range 0–29).
  • AP_DS_SUPPRESS_WARNINGS — suppress the GIL warning (default off).
  • AP_DS_SHOW_CONGRATS — show the "GIL disabled" congratulations message (default on).
  • AP_DS_SKIP_AUTO_CHECK — skip the import-time runtime self-check (default on).
  • AP_DS_HIDE_SUPPORT_PROMPT — hide the import banner (default off).
  • Full reference with defaults, accepted values, and examples.

7. Performance Optimization

  • Free-threading (Python 3.15t): GIL-less parallelism for batch parsing.
  • Batch parsing: ProcessPoolExecutor-based; tune max_workers (defaults to CPU count).
  • Memory management: audio caching, new_aid() preloading, clear_memory_cache() for long-running apps.
  • Short WAVs cached as Mix_Chunk; long files streamed via Mix_Music.

8. Cross-Platform Notes

  • Windows: SDL2 DLLs auto-downloaded from CDN with SHA-256 verification; os.add_dll_directory() and PATH handling.
  • macOS: SDL2 frameworks auto-downloaded as DMG and extracted.
  • Linux: no automatic download; package-manager installation (apt / dnf / pacman) or manual .so paths; LD_LIBRARY_PATH handling.

9. Troubleshooting Guide

  • "Failed to load music file" → verify the file exists, is readable, and the format is supported.
  • "SDL initialization failed" → check SDL2 binaries, antivirus on Windows, or install SDL2 dev packages on Linux.
  • "GIL is enabled" warning → upgrade to Python 3.15t, or suppress with AP_DS_SUPPRESS_WARNINGS=1.
  • DAP save failures → use the .ap-ds-dap extension and verify write permissions.

10. API Reference

  • AudioLibrary — full method reference with signatures, return types, and error codes (see the Method Return Type Summary).
  • Top-level convenience functions — batch_get_metadata, get_audio_duration, get_audio_metadata, show_tech_manual, is_full_performance, get_runtime_info.
  • audio_parser module API — parser classes, open_audio, StreamInfo.
  • SDL2 integration layer — constants, structures, and all function bindings.

11. Version History

  • Changelog from v1.0.0 through v4.0.0, including the architecture refactoring and the error-handling revolution.

12. Contributing & Support

  • Official home: apds.top.
  • Mirrors: GitCode (primary), GitHub (compatibility), Gitee (China).
  • License: MIT.
  • Author: DVS — me@dvsyun.top.

How to view the manual:

from ap_ds import show_tech_manual

show_tech_manual()   # prints the full manual to stdout

End of API Reference

This document covers all public and internal APIs for the ap_ds library version v4.0.0. For additional examples and usage patterns, please refer to the main README.md or visit https://apds.top.

Version History

v4.0.0 (August 2026) – Architecture Refactoring & Performance Edition

This is a complete rewrite of the core architecture following the v3.1.x bug reports.

🔧 Architecture Changes

1. Complete Module Restructuring

Before (v3.1.x) After (v4.0.0)
audio_parser.py (wrapper) + audio_info.py (actual parsers) → circular imports everywhere Merged everything into audio_parser.py – one file, no circular dependencies
player.py contained SDL2 loader + constants + bindings + AudioLibrary → 2000+ lines of chaos Split into player.py (AudioLibrary only) + _sdl2.py (loader + constants + bindings)
Import order mattered – changing imports could break things randomly Guaranteed import safety – no more "import roulette"

2. Bug Fixes

  • Fixed circular import bug causing get_audio_duration() and get_audio_metadata() to return 0 or None in specific environments
  • Fixed inconsistent import behavior across Python versions
  • Eliminated redundant wrapper layer that caused confusion

3. Unified Error Handling (New!)

  • All methods now return consistent error tuples: (error_code, error_message, suggestion)
  • Error codes for every failure type (see Error Codes Reference section)
  • Actionable suggestions included with every error
  • No more guessing what went wrong – code, message, and advice are all provided

4. Code Quality Improvements

  • Clean separation of concerns: each module has a single responsibility
  • No circular dependencies
  • Easier to debug and maintain
  • All internal APIs properly documented

5. Backward Compatibility

  • Top-level APIs are unchanged – AudioLibrary, batch_get_metadata(), get_audio_duration(), etc. work exactly as before
  • Only internal submodule imports (ap_ds.audio_info, ap_ds.player._sdl2) are affected
  • No breaking changes for normal usage

6. Windows BrokenProcessPool Protection (New!)

  • Added explicit warning about Windows ProcessPoolExecutor entry point requirements
  • All batch parsing methods now include documentation about if __name__ == "__main__" protection
  • Example code in docs includes proper entry point guards

7. GitHub Compatibility Mirror (New!)

  • New GitHub repository at dvs-dvsxt/ap_ds for GitHub-centric developers
  • Compatibility mirror only – primary home remains apds.top

v3.1.2 (July 2026) – Performance & Batch Parsing Edition (LFV)

This was a major feature release 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

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)

3. DAP Deduplication Optimization

_add_to_dap_recordings() upgraded from O(n) linear scan to O(1) set-based deduplication. O(n) fallback mechanism retained for stability.

4. Startup Acceleration: Lazy Import

Python 3.15+ users automatically benefit from lazy import – heavy modules are loaded on demand.

5. Runtime Diagnostic Functions

  • is_full_performance() – Checks if running in full-performance mode
  • get_runtime_info() – Returns diagnostic info

6. Runtime Self-Check

Automatically executes a runtime self-check on import (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 v3.1.2 8-process parallel 0.331s (2.94x faster)

Compared to v3.0.0:

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

⚠️ Known Issues

  • Critical bug: get_audio_duration() and get_audio_metadata() can return 0 or None in certain environments due to circular imports
  • Resolution: Fixed in v4.0.0

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.

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.

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://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://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://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:

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.


SDL2 Acknowledgments

A Sincere and Heartfelt Thank You

ap_ds would not exist without the incredible work of the SDL2 development team. We owe them a debt of gratitude that cannot be overstated.

To Sam Lantinga and the entire SDL development community:

Thank you. From the bottom of our hearts, thank you.

You have built something truly extraordinary. For over two decades, SDL has been the backbone of countless games, multimedia applications, and creative projects across the world. It runs on everything – Windows, macOS, Linux, Android, iOS, consoles, and embedded devices. It is stable, efficient, and beautifully designed. It is, quite simply, one of the most important open-source projects of our time.

We are just one small library among thousands that rely on your work. But we are deeply grateful. Every time a user plays an audio file through ap_ds, it is SDL2 doing the heavy lifting – decoding, mixing, and streaming audio with low latency and rock-solid reliability. We just provide the Python wrapper. You provide the magic.

What you have given the world:

  • A cross-platform multimedia library that actually works, without compromise
  • A clean, consistent API that developers love
  • Decades of maintenance, bug fixes, and performance improvements
  • A welcoming community that helps newcomers and experts alike
  • An open-source license that allows projects like ours to exist without legal barriers
  • The freedom to build, create, and share without fear

What we have learned from you:

  • The value of stability over chasing the latest trends
  • The importance of backward compatibility
  • How to write clean C code that stands the test of time
  • What it means to truly support developers across every platform

We are not worthy, but we are grateful.

This library uses the Simple DirectMedia Layer (SDL2) and SDL2_mixer libraries.

The zlib/libpng license is a permissive free software license that allows the software to be used, modified, and distributed freely, including in commercial products, with only minimal attribution requirements.

SDL2 License Text (zlib/libpng):

Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>

This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
  claim that you wrote the original software. If you use this software
  in a product, an acknowledgment in the product documentation would be
  appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
  misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

ap_ds includes precompiled binaries of SDL2 and SDL2_mixer for Windows and macOS platforms. These binaries are distributed under the same zlib/libpng license. The source code for these libraries is available at the official SDL2 website.

For Linux platforms, ap_ds uses system-installed SDL2 libraries or provides instructions for installation via package managers.

Thank You, SDL Team

To Sam Lantinga, Ryan C. Gordon, and everyone who has contributed to SDL over the years:

You are the unsung heroes of the open-source world.

While others chase billion-dollar valuations and fleeting trends, you quietly build the infrastructure that makes creativity possible. You ask for nothing in return but the satisfaction of seeing others build great things with your work.

We see you. We appreciate you. And we will never forget that everything we have built stands on your shoulders.

With deepest respect and gratitude,

The ap_ds Team


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!