Initial commit: ap_ds 4.0.1

This commit is contained in:
dvs
2026-08-27 19:21:49 +08:00
commit 034a1ec40d
13 changed files with 10835 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
# Python 缓存
__pycache__/
*.py[cod]
*.so
*.egg-info/
dist/
build/
.eggs/
# 版本/环境
.venv/
venv/
env/
.idea/
.vscode/
*.log
# 系统文件
Thumbs.db
.DS_Store
desktop.ini
+159
View File
@@ -0,0 +1,159 @@
# 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**:
- Email: me@dvsyun.top or dvs6666@163.com
- Project Homepage: https://apds.top
- Response Time: Within 7 business days
9.2. **Technical Support**:
- Priority should be given to submitting issues via GitCode Issues.
- Urgent matters can be directed to the emails above.
---
**BY USING, COPYING, MODIFYING, OR DISTRIBUTING THE SOFTWARE, YOU ACCEPT ALL TERMS AND CONDITIONS OF THIS LICENSE.**
---
+4122
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+173
View File
@@ -0,0 +1,173 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_all_apis.py - 测试 ap_ds 文档中所有 API 是否存在
包括 AudioLibrary 类的所有方法
"""
import sys
import inspect
print("=" * 60)
print("🧪 Testing All ap_ds API Exports")
print("=" * 60)
# ============================================================
# 1. 测试顶级函数导入
# ============================================================
TOP_LEVEL_APIS = [
"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",
]
print("\n📦 Testing top-level imports:")
print("-" * 40)
passed = 0
failed = 0
missing = []
for name in TOP_LEVEL_APIS:
try:
exec(f"from ap_ds import {name}")
print(f" ✅ {name}")
passed += 1
except ImportError as e:
print(f" ❌ {name}: {e}")
failed += 1
missing.append(name)
# ============================================================
# 2. 测试 AudioLibrary 类的所有方法
# ============================================================
AUDIOLIBRARY_METHODS = [
# 初始化
"__init__",
# 播放方法
"play_from_file",
"play_from_memory",
"new_aid",
# 控制方法
"play_audio",
"pause_audio",
"stop_audio",
"seek_audio",
# 音量方法
"set_volume",
"get_volume",
# 淡入淡出与过渡方法
"fadein_music",
"fadein_music_pos",
"fadeout_music",
"is_music_playing",
"is_music_paused",
"get_music_fading",
# 元数据方法
"get_audio_duration",
"get_audio_metadata",
"get_audio_metadata_by_path",
"get_audio_metadata_by_aid",
# 批量解析方法
"batch_get_metadata",
"batch_get_duration",
"batch_get_metadata_by_type",
# DAP 系统方法
"save_dap_to_json",
"get_dap_recordings",
"clear_dap_recordings",
# 资源管理
"clear_memory_cache",
"cleanup_function",
# 内部辅助方法 (文档中列出但通常是私有的)
"_find_channel_by_aid",
"_get_file_path_by_aid",
"_is_music_file",
"_seek_audio",
"_get_duration_by_filepath",
"_get_file_duration",
]
print("\n" + "-" * 40)
print("🎯 Testing AudioLibrary methods:")
print("-" * 40)
try:
from ap_ds import AudioLibrary
# 获取 AudioLibrary 类的所有方法
lib_methods = [m for m in dir(AudioLibrary) if not m.startswith('__') or m == '__init__']
for method_name in AUDIOLIBRARY_METHODS:
if hasattr(AudioLibrary, method_name):
print(f" ✅ AudioLibrary.{method_name}")
passed += 1
else:
print(f" ❌ AudioLibrary.{method_name} (NOT FOUND)")
failed += 1
missing.append(f"AudioLibrary.{method_name}")
except ImportError as e:
print(f" ❌ Cannot import AudioLibrary: {e}")
failed += 1
# ============================================================
# 3. 检查文档中可能遗漏的额外 API
# ============================================================
EXTRA_APIS = [
"is_full_performance",
"get_runtime_info",
]
print("\n" + "-" * 40)
print("🔍 Checking extra APIs (mentioned in docs but maybe not exported):")
print("-" * 40)
for name in EXTRA_APIS:
try:
exec(f"from ap_ds import {name}")
print(f" ✅ {name} (exists!)")
passed += 1
except ImportError:
print(f" ❌ {name} (NOT FOUND - remove from docs or add to __init__.py)")
failed += 1
missing.append(name)
# ============================================================
# 4. 汇总
# ============================================================
print("\n" + "=" * 60)
print("📊 FINAL SUMMARY")
print("=" * 60)
if failed == 0:
print("🎉 ALL APIs EXIST! Documentation is accurate.")
else:
print(f"⚠️ {failed} API(s) missing:")
for name in missing:
print(f" - {name}")
print("\n💡 Fix:")
print(" Either remove these from documentation, or add them to __init__.py")
print("=" * 60)
print(f"✅ Passed: {passed}")
print(f"❌ Failed: {failed}")
+9
View File
@@ -0,0 +1,9 @@
"""
ap_ds Test Suite
================
CI/CD, GUI and Import tests for the ap_ds audio library.
Note: Test scripts are included in the package for developers to run
after installation. They do not run automatically on import.
"""
__all__ = []
+686
View File
@@ -0,0 +1,686 @@
# __init__.py - Package entry point
import os
import sys
import warnings
try:
from ._version import __version__
except ImportError:
try:
from _version import __version__
except ImportError:
__version__ = "unknown"
# ============================================================
# Export top-level functions from audio_parser
# ============================================================
try:
from .audio_parser import (
batch_get_metadata,
batch_get_duration,
batch_get_metadata_by_type,
get_audio_duration,
get_audio_metadata,
)
except ImportError:
try:
from audio_parser import (
batch_get_metadata,
batch_get_duration,
batch_get_metadata_by_type,
get_audio_duration,
get_audio_metadata,
)
except ImportError:
# Define as None if audio_parser not available
batch_get_metadata = None
batch_get_duration = None
batch_get_metadata_by_type = None
get_audio_duration = None
get_audio_metadata = None
def is_full_performance() -> bool:
"""Check if running in full performance mode."""
info = _auto_check_runtime()
return info.get('is_full_performance', False) if info else False
def get_runtime_info() -> dict:
"""Get runtime information dictionary."""
info = _auto_check_runtime()
return info.copy() if info else {}
# ============================================================
# Banner
# ============================================================
if os.environ.get('AP_DS_HIDE_SUPPORT_PROMPT') != '1':
print(f"AP_DS © - Audio Library By DVS v{__version__} | https://apds.top")
# ============================================================
# Runtime Environment Detection
# ============================================================
SUPPRESS_WARNINGS = os.environ.get('AP_DS_SUPPRESS_WARNINGS', '').lower() in ('1', 'true', 'yes', 'on')
SHOW_CONGRATS = os.environ.get('AP_DS_SHOW_CONGRATS', '').lower() not in ('0', 'false', 'no', 'off')
_AUTO_CHECK_SKIP = os.environ.get('AP_DS_SKIP_AUTO_CHECK', '1').lower() in ('1', 'true', 'yes', 'on')
# ============================================================
# Show Technical Manual (User-Initiated)
# ============================================================
def show_tech_manual() -> None:
"""
Display the complete AP_DS 4.0.1 Technical Manual.
This function prints a comprehensive technical reference including:
- Library architecture
- Supported audio formats
- Core components description
- API reference
- Performance tuning
- Environment variables
- Cross-platform notes
- Troubleshooting guide
User must call this function explicitly. It will NOT be called automatically.
Examples:
>>> from ap_ds import show_tech_manual
>>> show_tech_manual()
"""
manual = r"""
╔═══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ AP_DS 4.0.1 TECHNICAL MANUAL ║
║ Audio Library By DVS - https://apds.top ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════╝
┌───────────────────────────────────────────────────────────────────────────────┐
│ 1. OVERVIEW │
└───────────────────────────────────────────────────────────────────────────────┘
AP_DS (Audio Playback & Data Service) is a cross-platform, high-performance
audio library for Python applications. Built on SDL2 and SDL2_mixer, it provides:
• Low-latency audio playback
• Accurate metadata parsing (pure Python, no external dependencies)
• Smart WAV handling with automatic mode switching
• DAP (Dvs Audio Playlist) recording with O(1) deduplication
• Batch metadata extraction with multi-core parallelism
• Fade in/out controls with position seeking
• Memory-efficient caching with automatic cleanup
┌───────────────────────────────────────────────────────────────────────────────┐
│ 2. SUPPORTED AUDIO FORMATS │
└───────────────────────────────────────────────────────────────────────────────┘
┌──────────────┬─────────────┬─────────────────────────────────────────┐
│ Format │ Extension │ Notes │
├──────────────┼─────────────┼─────────────────────────────────────────┤
│ MP3 │ .mp3 │ Frame-by-frame scanning, >98% accuracy │
│ WAV │ .wav │ RIFF chunk parsing, 100% accuracy │
│ FLAC │ .flac │ STREAMINFO block, 100% accuracy │
│ OGG Vorbis │ .ogg │ Granule position, 99.99% accuracy │
│ AAC (ADTS) │ .aac │ ADTS frame parsing, >99% accuracy │
└──────────────┴─────────────┴─────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────────┐
│ 3. CORE COMPONENTS │
└───────────────────────────────────────────────────────────────────────────────┘
3.1 AudioLibrary (player.py)
────────────────────────────
Main class providing all audio playback and management functionality.
Methods:
• play_from_file(file_path, loops=0, start_pos=0.0) -> int
Play audio directly from file, returns AID
• play_from_memory(file_path, loops=0, start_pos=0.0) -> int
Play audio from memory cache, returns AID
• new_aid(file_path) -> int
Generate AID without playing (preloads to cache)
• pause_audio(aid) -> None
Pause audio playback
• stop_audio(aid) -> float
Stop playback and return played duration
• seek_audio(aid, position) -> None
Seek to specified position in seconds
• set_volume(aid, volume) -> bool
Set volume (0-128)
• get_volume(aid) -> int
Get current volume
• fadein_music(aid, loops=-1, ms=0) -> bool
Fade in music
• fadein_music_pos(aid, loops=-1, ms=0, position=0.0) -> bool
Fade in music from position
• fadeout_music(ms=0) -> bool
Fade out music
• clear_memory_cache() -> None
Clear all cached audio data
• save_dap_to_json(save_path) -> bool
Save DAP recordings to .ap-ds-dap file
• get_dap_recordings() -> List[Dict]
Get current DAP recordings
• clear_dap_recordings() -> None
Clear all DAP recordings
3.2 Metadata Parsers (audio_parser.py)
──────────────────────────────────────
Pure-Python parsers for audio metadata extraction.
Functions:
• get_audio_duration(file_path) -> int
Get duration in seconds
• get_audio_metadata(file_path) -> Dict
Get complete metadata (duration, sample_rate, channels, bitrate)
• batch_get_metadata(file_paths, max_workers=None, show_progress=False) -> List[Dict]
Parse multiple files in parallel
• batch_get_duration(file_paths, max_workers=None) -> Dict[str, int]
Get durations for multiple files
• batch_get_metadata_by_type(file_paths, file_type, max_workers=None) -> List[Dict]
Filter results by format
3.3 SDL2 Loader (_sdl2.py)
──────────────────────────
Cross-platform SDL2 library loader with automatic fallback.
Loading order (Linux):
1. Package directory
2. User config (~/.config/ap_ds/sdl_paths.conf)
3. System libraries
4. Auto-install via package manager
5. Interactive setup
Loading order (Windows/macOS):
1. Package directory
2. System path
3. Automatic download from CDN
┌───────────────────────────────────────────────────────────────────────────────┐
│ 4. DAP (Dvs Audio Playlist) SYSTEM │
└───────────────────────────────────────────────────────────────────────────────┘
The DAP system automatically records every audio file that is played or loaded
through the AudioLibrary. Features:
• O(1) Deduplication: Uses Python set for fast duplicate checking
• Fallback O(n): Linear scan if set deduplication fails
• Persistent Storage: Save to .ap-ds-dap JSON files
• Memory Efficient: Stores only metadata, not audio data
Record Structure:
{
"path": "/path/to/audio.mp3",
"duration": 240,
"bitrate": 320000,
"channels": 2
}
┌───────────────────────────────────────────────────────────────────────────────┐
│ 5. WAV SMART MODE │
└───────────────────────────────────────────────────────────────────────────────┘
WAV files are automatically handled differently based on duration:
┌────────────────────┬─────────────────┬────────────────────────────────┐
│ Duration │ Mode │ SDL2 API Used │
├────────────────────┼─────────────────┼────────────────────────────────┤
│ < WAV_THRESHOLD │ Sound Effect │ Mix_PlayChannel (memory) │
│ >= WAV_THRESHOLD │ Music │ Mix_PlayMusic (streaming) │
└────────────────────┴─────────────────┴────────────────────────────────┘
Default threshold: 6 seconds
Configure via: AP_DS_WAV_THRESHOLD environment variable
┌───────────────────────────────────────────────────────────────────────────────┐
│ 6. ENVIRONMENT VARIABLES │
└───────────────────────────────────────────────────────────────────────────────┘
AP_DS_WAV_THRESHOLD
─────────────────
WAV mode switching threshold in seconds.
Default: 6
Range: 0-29 (values >=30 reset to 6)
Example: AP_DS_WAV_THRESHOLD=10
AP_DS_SUPPRESS_WARNINGS
─────────────────────
Suppress GIL warning messages.
Default: 0 (warnings enabled)
Values: 1, true, yes, on
Example: AP_DS_SUPPRESS_WARNINGS=1
AP_DS_SHOW_CONGRATS
─────────────────
Show congratulations message when GIL is disabled.
Default: 1 (show)
Values: 0, false, no, off (to hide)
Example: AP_DS_SHOW_CONGRATS=0
AP_DS_SKIP_AUTO_CHECK
───────────────────
Skip runtime self-check on import.
Default: 1 (skip)
Values: 1, true, yes, on (to skip)
Example: AP_DS_SKIP_AUTO_CHECK=0 # Show self-check
AP_DS_HIDE_SUPPORT_PROMPT
──────────────────────
Hide the support prompt banner.
Default: 0 (show banner)
Values: 1
Example: AP_DS_HIDE_SUPPORT_PROMPT=1
┌───────────────────────────────────────────────────────────────────────────────┐
│ 7. PERFORMANCE OPTIMIZATION │
└───────────────────────────────────────────────────────────────────────────────┘
7.1 Free-Threading Support
──────────────────────────
AP_DS 4.0.1 is optimized for Python 3.15t (free-threading mode).
When running with GIL disabled, performance improves significantly:
• Batch metadata parsing uses ProcessPoolExecutor
• Multiple audio operations can run in parallel
• Lower latency for concurrent playback
To enable free-threading:
Download Python 3.15t from:
https://mirrors.huaweicloud.com/python/3.15.0/python-3.15.0b4t-amd64.zip
7.2 Batch Processing
────────────────────
Use batch APIs for processing multiple files:
metadata = batch_get_metadata(directory, max_workers=4, show_progress=True)
Workers default to CPU count. Adjust based on:
• I/O bound: Use more workers (CPU count * 2)
• CPU bound: Use CPU count (or CPU count - 1 on 4+ cores)
7.3 Memory Management
─────────────────────
Audio data is cached in memory. To manage memory:
• Use new_aid() to preload without playing
• Call clear_memory_cache() periodically for long-running apps
• WAV files under 6 seconds are cached as Mix_Chunk in memory
• WAV files over 6 seconds stream via Mix_Music
┌───────────────────────────────────────────────────────────────────────────────┐
│ 8. CROSS-PLATFORM NOTES │
└───────────────────────────────────────────────────────────────────────────────┘
8.1 Windows
───────────
• DLLs automatically downloaded from CDN
• SDL2.dll and SDL2_mixer.dll placed in package directory
• os.add_dll_directory() used for modern Windows
• PATH environment variable updated automatically
8.2 macOS
─────────
• Frameworks downloaded as DMG and auto-extracted
• SDL2.framework and SDL2_mixer.framework
• DYLD_FRAMEWORK_PATH updated automatically
• Supports both Intel (x64) and Apple Silicon (ARM)
8.3 Linux
─────────
• No automatic download (distribution compatibility)
• Uses system package manager when possible
• Manual installation instructions provided
• Supports: Ubuntu/Debian (apt), Fedora (dnf), Arch (pacman)
• LD_LIBRARY_PATH updated when loading from package
┌───────────────────────────────────────────────────────────────────────────────┐
│ 9. TROUBLESHOOTING │
└───────────────────────────────────────────────────────────────────────────────┘
9.1 "Failed to load music file"
──────────────────────────────
• Verify file exists and is readable
• Check if SDL2_mixer supports the format
• For WAV files > 6s, ensure file is valid PCM
9.2 "SDL initialization failed"
────────────────────────────────
• SDL2 library not loaded properly
• On Windows, check antivirus isn't blocking DLLs
• On Linux, install SDL2 development packages
9.3 "audio_parser not available"
──────────────────────────────────
• audio_parser.py missing from package
• Reinstall ap_ds: pip install --upgrade ap_ds
9.4 "GIL is enabled" warning
──────────────────────────────
• Running on standard Python (non-free-threading)
• Upgrade to Python 3.15t for full performance
• Or suppress with AP_DS_SUPPRESS_WARNINGS=1
9.5 DAP recordings not saving
──────────────────────────────
• Check file extension: must be .ap-ds-dap
• Verify write permissions on save location
• Ensure at least one file was played/loaded
┌───────────────────────────────────────────────────────────────────────────────┐
│ 10. API REFERENCE │
└───────────────────────────────────────────────────────────────────────────────┘
10.1 AudioLibrary Class
───────────────────────
class AudioLibrary(frequency=44100, format=MIX_DEFAULT_FORMAT,
channels=2, chunksize=2048)
参数:
frequency: Audio sample rate (Hz)
format: Audio format (MIX_DEFAULT_FORMAT)
channels: Number of channels (1=mono, 2=stereo)
chunksize: Audio buffer size
10.2 AID (Audio ID) System
──────────────────────────
Every audio playback/load returns a unique AID.
Use AID to control playback:
aid = lib.play_from_file("song.mp3")
lib.pause_audio(aid)
lib.seek_audio(aid, 30.0)
lib.stop_audio(aid)
10.3 Channel vs Music
─────────────────────
Sound Effect Mode (Mix_PlayChannel):
• Up to 8 simultaneous sounds
• Loaded into memory (Mix_Chunk)
• Low latency
• Best for short sounds (<6s)
Music Mode (Mix_PlayMusic):
• One at a time
• Streamed from disk (Mix_Music)
• Supports seeking and fading
• Best for long tracks (>=6s)
┌───────────────────────────────────────────────────────────────────────────────┐
│ 11. VERSION HISTORY │
└───────────────────────────────────────────────────────────────────────────────┘
Version 4.0.1 (Current)
────────────────────────
• Python 3.15t free-threading support
• Lazy imports for Python 3.15+
• DAP O(1) deduplication
• Batch metadata extraction
• Smart WAV mode switching
• Audio metadata parsers (pure Python)
• Cross-platform SDL2 loader
Version 3.x
───────────
• Initial SDL2 bindings
• Audio playback and control
• Volume control
• Basic metadata support
┌───────────────────────────────────────────────────────────────────────────────┐
│ 12. CONTRIBUTING & SUPPORT │
└───────────────────────────────────────────────────────────────────────────────┘
Website: https://apds.top
Source Code: https://gitcode.com/dvsxt/ap_ds
Documentation: https://apds.top/docs
Issues: https://gitcode.com/dvsxt/ap_ds/issues
License: MIT
Author: DVS
Email: support@apds.top
╔═══════════════════════════════════════════════════════════════════════════════╗
║ END OF MANUAL ║
║ AP_DS 4.0.1 - December 2025 ║
║ ║
║ 📖 For detailed Markdown documentation, visit: ║
║ https://apds.top ║
║ https://gitcode.com/dvsxt/ap_ds ║
║ ║
║ 📝 View source code: ║
║ https://gitcode.com/dvsxt/ap_ds ║
║ ║
║ 💬 Report issues: ║
║ https://gitcode.com/dvsxt/ap_ds/issues ║
║ ║
║ 💡 Quick start: ║
║ from ap_ds import AudioLibrary ║
║ lib = AudioLibrary() ║
║ aid = lib.play_from_file("music.mp3") ║
╚═══════════════════════════════════════════════════════════════════════════════╝
"""
print(manual)
def _check_runtime_mode():
"""
Check GIL status and notify the user accordingly.
Returns:
bool: True if GIL is enabled, False if disabled (free-threading)
"""
try:
gil_enabled = sys._is_gil_enabled()
except AttributeError:
gil_enabled = True
if not gil_enabled:
if SHOW_CONGRATS:
print("🎉 ap_ds: GIL disabled (free-threading mode)")
else:
if not SUPPRESS_WARNINGS:
warnings.warn(
"⚠️ ap_ds: GIL is enabled (multi-core parallelism limited).\n"
" For full performance, upgrade to Python 3.15t:\n"
" https://mirrors.huaweicloud.com/python/3.15.0/python-3.15.0b4t-amd64.zip\n"
" To suppress this warning, set AP_DS_SUPPRESS_WARNINGS=1",
RuntimeWarning,
stacklevel=2
)
return gil_enabled
def _auto_check_runtime():
"""
Automatic runtime self-check on library import.
Prints diagnostic information including:
- Python version
- GIL status
- Profiling availability
- Performance mode
- CPU cores
- Platform
- Library info (name, version, install path, website, author)
Can be disabled by setting environment variable:
AP_DS_SKIP_AUTO_CHECK=1
Returns:
dict: Runtime information dictionary
"""
if _AUTO_CHECK_SKIP:
return None
print("\n" + "=" * 60)
print("🔍 ap_ds Runtime Self-Check")
print("=" * 60)
# Library Info
print(f"📚 Library: AP_DS (Audio Library By DVS)")
print(f"📌 Version: {__version__}")
print(f"📂 Install Path: {os.path.dirname(os.path.abspath(__file__))}")
print(f"🌐 Website: https://apds.top")
print(f"📦 PyPI: https://pypi.org/project/ap-ds/")
print(f"📦 Mirror: https://pypi.tuna.tsinghua.edu.cn/simple/ap-ds/")
print()
print("📥 Installation:")
print(" pip install ap-ds==4.0.1")
print(" pip install ap-ds==4.0.1 -i https://pypi.tuna.tsinghua.edu.cn/simple")
print(" pip install /path/to/ap-ds-4.0.1-py3-none-any.whl")
print(f"👤 Author: DVS")
print()
print("📖 Description:")
print(" AP_DS (Audio Playback & Data Service) is a cross-platform audio")
print(" library built on SDL2 and SDL2_mixer, designed for Python applications")
print(" requiring high-performance audio playback and metadata management.")
print()
print(" Core Features:")
print(" • Audio Playback: MP3, WAV, FLAC, OGG, AAC, and more")
print(" • Smart WAV Handling: Auto-switch between music/sound effect mode")
print(" • Metadata Parsing: Duration, sample rate, channels, bitrate")
print(" • DAP Recording: O(1) deduplication playlist generation")
print(" • Batch Processing: Multi-core parallel metadata extraction")
print(" • Fade Control: Fade in/out with position seeking support")
print(" • Memory Management: Efficient caching with automatic cleanup")
print()
print(" Performance:")
print(" • Native SDL2 bindings with zero-copy audio processing")
print(" • Free-threading support (Python 3.15t) for maximum parallelism")
print(" • ProcessPoolExecutor for CPU-bound batch operations")
print()
print(" Platform Support:")
print(" • Windows (x64) • macOS (x64/ARM) • Linux (x64/ARM)")
print()
print(" Documentation: https://apds.top/docs")
print(" Source Code: https://gitcode.com/dvsxt/ap_ds")
print(" License: DVS Audio Library (ap_ds) Open Source License Version 2.0")
print()
print(f"🐍 Python: {sys.version.split()[0]} ({sys.implementation.name})")
try:
gil_enabled = sys._is_gil_enabled()
print(f"🔒 GIL: {'Enabled' if gil_enabled else 'Disabled (Free-Threading)! 🎉'}")
except AttributeError:
gil_enabled = True
print(f"🔒 GIL: Unknown (pre-3.14, assumed Enabled)")
try:
import profiling
has_profiling = True
print(f"📊 Profiling: Available (Python 3.15+)")
except ImportError:
has_profiling = False
print(f"📊 Profiling: Not available (requires Python 3.15+)")
is_full = has_profiling and not gil_enabled
print(f"🚀 Full Performance Mode: {'✅ YES! (3.15t)' if is_full else '❌ No (degraded mode)'}")
print(f"💻 CPU Cores: {os.cpu_count() or 0}")
print(f"🖥️ Platform: {sys.platform}")
print("=" * 60)
if not is_full:
print("💡 Tip: Upgrade to Python 3.15t for full performance:")
print(" https://mirrors.huaweicloud.com/python/3.15.0/python-3.15.0b4t-amd64.zip")
print(" To suppress this auto-check, set AP_DS_SKIP_AUTO_CHECK=1")
else:
print("🎉 You're running in full performance mode! Enjoy!")
print("=" * 60 + "\n")
return {
"library_name": "AP_DS",
"library_version": __version__,
"library_install_path": os.path.dirname(os.path.abspath(__file__)),
"library_website": "https://apds.top",
"library_author": "DVS",
"python_version": sys.version.split()[0],
"gil_enabled": gil_enabled,
"has_profiling": has_profiling,
"is_full_performance": is_full,
"cpu_count": os.cpu_count() or 0,
"platform": sys.platform,
}
# ============================================================
# Runtime Self-Check on Import
# ============================================================
_RUNTIME_CHECKED = False
def ensure_runtime_checked():
"""Ensure runtime check is performed only once."""
global _RUNTIME_CHECKED
if not _RUNTIME_CHECKED:
_check_runtime_mode()
_RUNTIME_CHECKED = True
ensure_runtime_checked()
# Execute auto self-check on import (user can call again later)
_auto_check_runtime()
# ============================================================
# Import Player Module (AudioLibrary and all core functions)
# ============================================================
try:
from .player import *
except ImportError:
from player import *
# ============================================================
# Export Self-Check Functions (users can call manually)
# ============================================================
try:
# Try direct assignment first (functions already defined in this module)
auto_check_runtime = _auto_check_runtime
check_runtime_mode = _check_runtime_mode
except Exception:
# Fallback: import from current package
try:
from . import _auto_check_runtime as auto_check_runtime
from . import _check_runtime_mode as check_runtime_mode
except Exception:
# Final fallback: define as None
auto_check_runtime = None
check_runtime_mode = None
# ============================================================
# Public API
# ============================================================
# __init__.py
__all__ = [
"__version__",
"AudioLibrary",
"get_audio_duration",
"get_audio_metadata",
"batch_get_metadata",
"batch_get_duration",
"batch_get_metadata_by_type",
"auto_check_runtime",
"check_runtime_mode",
"show_tech_manual",
]
+1008
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
__version__ = "4.0.1"
+649
View File
@@ -0,0 +1,649 @@
"""
audio_parser.py - Format-Specific Audio Metadata Parsers
This module provides pure-Python parsers for extracting metadata (duration,
sample rate, channels, bitrate) from various audio formats without external
dependencies.
Supported formats:
- WAV: 100% accuracy (RIFF chunk parsing)
- FLAC: 100% accuracy (STREAMINFO block)
- MP3: >98% accuracy (frame-by-frame scanning)
- AAC: >99% accuracy (ADTS frame parsing)
- OGG Vorbis: 99.99% accuracy (granule position)
Python 3.14/3.15 optimizations:
- Batch parsing uses ProcessPoolExecutor for true parallelism
- Runtime mode detection: Warns users when running with GIL enabled
Environment variables:
AP_DS_SUPPRESS_WARNINGS=1 - Suppress GIL warning
AP_DS_SHOW_CONGRATS=0 - Hide "GIL disabled" congratulations message
"""
import os
import sys
import struct
import io
import warnings
from concurrent.futures import ProcessPoolExecutor, as_completed
from typing import List, Dict, Optional, Union, Tuple
from pathlib import Path
# Error codes (mirror player.py values; defined locally to avoid circular import)
AP_DS_ERR_UNKNOWN = 1999
# ============================================================
# Runtime Environment Detection
# ============================================================
SUPPRESS_WARNINGS = os.environ.get('AP_DS_SUPPRESS_WARNINGS', '').lower() in ('1', 'true', 'yes', 'on')
SHOW_CONGRATS = os.environ.get('AP_DS_SHOW_CONGRATS', '').lower() not in ('0', 'false', 'no', 'off')
_RUNTIME_CHECKED = False
def _check_runtime_mode():
"""
Detect GIL status and notify the user accordingly.
"""
try:
gil_enabled = sys._is_gil_enabled()
except AttributeError:
gil_enabled = True # Pre-3.14 always has GIL
if not gil_enabled:
if SHOW_CONGRATS:
print("🎉 ap_ds: GIL disabled (free-threading mode)")
else:
if not SUPPRESS_WARNINGS:
warnings.warn(
"⚠️ ap_ds: GIL is enabled (multi-core parallelism limited).\n"
" For full performance, upgrade to Python 3.15t:\n"
" https://mirrors.huaweicloud.com/python/3.15.0/python-3.15.0b4t-amd64.zip\n"
" To suppress this warning, set AP_DS_SUPPRESS_WARNINGS=1",
RuntimeWarning,
stacklevel=2
)
return gil_enabled
def _ensure_runtime_checked():
"""Ensure runtime mode check is performed only once per process."""
global _RUNTIME_CHECKED
if not _RUNTIME_CHECKED:
_check_runtime_mode()
_RUNTIME_CHECKED = True
_ensure_runtime_checked()
# ============================================================
# Core Data Structures
# ============================================================
class StreamInfo:
"""
Container for audio stream metadata.
Attributes:
length (float): Duration in seconds
sample_rate (int): Sample rate in Hz
channels (int): Number of audio channels (1=mono, 2=stereo)
bitrate (int): Bitrate in bits per second
"""
__slots__ = ("length", "sample_rate", "channels", "bitrate")
def __init__(self, length, sample_rate, channels, bitrate):
self.length = float(length)
self.sample_rate = int(sample_rate)
self.channels = int(channels)
self.bitrate = int(bitrate)
def __repr__(self):
return (
f"<StreamInfo length={self.length:.6f}s "
f"rate={self.sample_rate}Hz "
f"channels={self.channels} "
f"bitrate={self.bitrate}bps>"
)
class FileType:
"""
Base class for format-specific parsers.
Each subclass must implement _parse() to return a StreamInfo object.
"""
__slots__ = ("filename", "info")
def __init__(self, filename):
self.filename = filename
self.info = self._parse()
def _parse(self):
raise ValueError("Invalid audio file")
@property
def length(self):
return self.info.length
@property
def sample_rate(self):
return self.info.sample_rate
@property
def channels(self):
return self.info.channels
@property
def bitrate(self):
return self.info.bitrate
# ============================================================
# Utility Functions
# ============================================================
def open_file(path):
"""Open a file in binary read mode."""
return open(path, "rb")
def read_u32_be(f):
"""Read a big-endian 32-bit unsigned integer from a file."""
return struct.unpack(">I", f.read(4))[0]
def read_u32_le(f):
"""Read a little-endian 32-bit unsigned integer from a file."""
return struct.unpack("<I", f.read(4))[0]
def read_u16_le(f):
"""Read a little-endian 16-bit unsigned integer from a file."""
return struct.unpack("<H", f.read(2))[0]
# ============================================================
# WAV Parser (100% accuracy)
# ============================================================
class WAVFile(FileType):
"""
WAV audio parser using RIFF chunk structure.
Extracts format information from the 'fmt ' chunk and data size from
the 'data' chunk. Computes duration from total frames and sample rate.
Accuracy: 100% (based on file structure, no heuristics).
"""
def _parse(self):
with open_file(self.filename) as f:
if f.read(4) != b"RIFF":
raise ValueError
f.read(4)
if f.read(4) != b"WAVE":
raise ValueError
sample_rate = channels = block_align = data_size = None
while True:
chunk = f.read(4)
if not chunk:
break
size = read_u32_le(f)
if chunk == b"fmt ":
fmt = f.read(size)
channels = struct.unpack("<H", fmt[2:4])[0]
sample_rate = struct.unpack("<I", fmt[4:8])[0]
block_align = struct.unpack("<H", fmt[12:14])[0]
elif chunk == b"data":
data_size = size
break
else:
f.seek(size, io.SEEK_CUR)
total_frames = data_size // block_align
length = total_frames / sample_rate
bitrate = sample_rate * block_align * 8 // channels
return StreamInfo(length, sample_rate, channels, bitrate)
# ============================================================
# FLAC Parser (100% accuracy)
# ============================================================
class FLACFile(FileType):
"""
FLAC audio parser reading the STREAMINFO metadata block.
The STREAMINFO block is mandatory in all FLAC files and contains
sample rate, channel count, and total samples. Accuracy: 100%.
"""
def _parse(self):
with open_file(self.filename) as f:
if f.read(4) != b"fLaC":
raise ValueError
while True:
header = f.read(4)
is_last = header[0] & 0x80
block_type = header[0] & 0x7F
size = struct.unpack(">I", b"\x00" + header[1:4])[0]
if block_type == 0: # STREAMINFO
data = f.read(size)
sample_rate = (
(data[10] << 12)
| (data[11] << 4)
| (data[12] >> 4)
)
channels = ((data[12] >> 1) & 0x07) + 1
total_samples = (
((data[13] & 0x0F) << 32)
| (data[14] << 24)
| (data[15] << 16)
| (data[16] << 8)
| data[17]
)
length = total_samples / sample_rate
bitrate = os.path.getsize(self.filename) * 8 / length
return StreamInfo(length, sample_rate, channels, bitrate)
else:
f.seek(size, io.SEEK_CUR)
if is_last:
break
raise ValueError
# ============================================================
# MP3 Parser (frame-by-frame scanning, >98% accuracy)
# ============================================================
# MP3 bitrate lookup table (indexed by header bits)
MP3_BITRATES = [
None, 32, 40, 48, 56, 64, 80, 96,
112, 128, 160, 192, 224, 256, 320, None
]
# MP3 sample rate lookup table (indexed by header bits)
MP3_SAMPLE_RATES = [44100, 48000, 32000, None]
class MP3File(FileType):
"""
MP3 audio parser using frame-by-frame scanning.
Scans the file for MP3 frame sync words (0xFF), counts frames, and
accumulates samples. Accuracy: >98% (limited by variable bitrate
and incomplete final frames).
"""
def _parse(self):
filesize = os.path.getsize(self.filename)
total_frames = 0
with open_file(self.filename) as f:
while True:
b = f.read(1)
if not b:
break
if b != b"\xff":
continue
hdr = f.read(3)
if len(hdr) < 3:
break
if hdr[0] & 0xE0 != 0xE0:
f.seek(-3, 1)
continue
bitrate = MP3_BITRATES[(hdr[1] >> 4) & 0x0F]
sample_rate = MP3_SAMPLE_RATES[(hdr[1] >> 2) & 0x03]
if not bitrate or not sample_rate:
f.seek(-3, 1)
continue
frame_len = int(144000 * bitrate / sample_rate)
total_frames += 1
f.seek(frame_len - 4, 1)
length = total_frames * 1152 / sample_rate
bitrate = filesize * 8 / length
return StreamInfo(length, sample_rate, 2, bitrate)
# ============================================================
# AAC (ADTS) Parser (frame-by-frame, >99% accuracy)
# ============================================================
AAC_SAMPLE_RATES = [
96000, 88200, 64000, 48000, 44100, 32000,
24000, 22050, 16000, 12000, 11025, 8000
]
class AACFile(FileType):
"""
AAC audio parser using ADTS (Audio Data Transport Stream) frame parsing.
Scans for ADTS sync words (0xFFF), parses frame headers to accumulate
samples. Each AAC frame contains 1024 samples. Accuracy: >99%.
"""
def _parse(self):
total_samples = 0
with open_file(self.filename) as f:
while True:
header = f.read(7)
if len(header) < 7:
break
if header[0] != 0xFF or (header[1] & 0xF0) != 0xF0:
break
sr = AAC_SAMPLE_RATES[(header[2] >> 2) & 0x0F]
channels = ((header[2] & 1) << 2) | ((header[3] >> 6) & 3)
frame_length = (
((header[3] & 0x03) << 11)
| (header[4] << 3)
| (header[5] >> 5)
)
total_samples += 1024
f.seek(frame_length - 7, 1)
length = total_samples / sr
bitrate = os.path.getsize(self.filename) * 8 / length
return StreamInfo(length, sr, channels, bitrate)
# ============================================================
# OGG Vorbis Parser (granule position, 99.99% accuracy)
# ============================================================
class OGGFile(FileType):
"""
OGG Vorbis audio parser using granule position.
Reads Ogg pages, extracts the granule position (total samples) from
the last page. Also parses the Vorbis identification header for
sample rate and channel count. Accuracy: 99.99%.
"""
def _parse(self):
filesize = os.path.getsize(self.filename)
with open_file(self.filename) as f:
sample_rate = channels = None
last_granule = 0
while True:
header = f.read(27)
if len(header) < 27:
break
if header[:4] != b"OggS":
break
granule = struct.unpack("<Q", header[6:14])[0]
last_granule = max(last_granule, granule)
seg_count = header[26]
seg_sizes = f.read(seg_count)
f.seek(sum(seg_sizes), 1)
if sample_rate is None:
pos = f.tell()
f.seek(-sum(seg_sizes), 1)
packet = f.read(seg_sizes[0])
if packet.startswith(b"\x01vorbis"):
channels = packet[11]
sample_rate = struct.unpack("<I", packet[12:16])[0]
f.seek(pos, 0)
length = last_granule / sample_rate
bitrate = filesize * 8 / length
return StreamInfo(length, sample_rate, channels, bitrate)
# ============================================================
# Factory Function
# ============================================================
def open_audio(filename):
"""
Factory function that returns the appropriate parser instance.
Args:
filename: Path to the audio file
Returns:
FileType: Parser instance (WAVFile, FLACFile, MP3File, AACFile, or OGGFile)
Raises:
ValueError: If the file format is unsupported
"""
ext = os.path.splitext(filename)[1].lower()
if ext == ".wav":
return WAVFile(filename)
if ext == ".flac":
return FLACFile(filename)
if ext == ".mp3":
return MP3File(filename)
if ext == ".aac":
return AACFile(filename)
if ext == ".ogg":
return OGGFile(filename)
raise ValueError(f"Unsupported audio format: {ext}")
# ============================================================
# Single File Parsing
# ============================================================
def _parse_single_file(file_path: str) -> Optional[Dict]:
"""
Parse a single audio file and return metadata as a dictionary.
Internal helper for batch operations. Returns None on failure.
Args:
file_path: Path to the audio file
Returns:
dict or None: Metadata dict with keys:
path, format, duration, length, sample_rate, channels, bitrate
"""
try:
audio = open_audio(file_path)
info = audio.info
ext = os.path.splitext(file_path)[1].lower().lstrip(".")
return {
"path": file_path,
"format": ext,
"duration": int(info.length),
"length": float(info.length),
"sample_rate": info.sample_rate,
"channels": info.channels,
"bitrate": info.bitrate,
}
except Exception:
return None
# ============================================================
# Batch Processing API (ProcessPoolExecutor)
# ============================================================
def batch_get_metadata(
file_paths: Union[List[str], str],
max_workers: Optional[int] = None,
show_progress: bool = False
) -> List[Dict]:
"""
Parse multiple audio files in parallel using multiprocessing.
ProcessPoolExecutor avoids file handle contention issues on Windows
with free-threading Python builds.
Args:
file_paths: List of file paths, or a single directory path string.
If a directory is provided, all supported audio files
in that directory are scanned recursively.
max_workers: Maximum number of worker processes. Defaults to CPU count.
show_progress: If True, prints progress to stdout.
Returns:
List[Dict]: List of metadata dictionaries. Failed parses are omitted.
Examples:
>>> results = batch_get_metadata(["song1.mp3", "song2.flac"])
>>> results = batch_get_metadata("/music/playlist/", show_progress=True)
"""
# If a directory is given, expand to list of files
if isinstance(file_paths, (str, Path)):
dir_path = Path(file_paths)
if dir_path.is_dir():
supported_exts = {'.mp3', '.wav', '.flac', '.ogg', '.aac'}
file_paths = [
str(p) for p in dir_path.rglob('*')
if p.suffix.lower() in supported_exts and p.is_file()
]
else:
file_paths = [str(file_paths)]
if not file_paths:
return []
if max_workers is None:
max_workers = min(os.cpu_count() or 4, len(file_paths))
# Invalid max_workers: build the pool and, on failure, return an error
# tuple for the caller to handle (the library does not raise).
try:
executor = ProcessPoolExecutor(max_workers=max_workers)
except (ValueError, TypeError) as e:
return (AP_DS_ERR_UNKNOWN, f"Invalid max_workers: {e}",
"max_workers must be a positive integer or None for automatic")
results = []
total = len(file_paths)
completed = 0
with executor:
future_to_path = {
executor.submit(_parse_single_file, path): path
for path in file_paths
}
for future in as_completed(future_to_path):
path = future_to_path[future]
completed += 1
if show_progress and completed % 10 == 0:
print(f"Progress: {completed}/{total} files parsed")
try:
metadata = future.result()
if metadata:
results.append(metadata)
else:
print(f"⚠️ Parse failed: {os.path.basename(path)}")
except Exception as e:
print(f"❌ Parse error [{os.path.basename(path)}]: {type(e).__name__}: {e}")
if show_progress:
print(f"✅ Batch parse complete: {len(results)}/{total} files successful")
return results
def batch_get_duration(
file_paths: Union[List[str], str],
max_workers: Optional[int] = None
) -> Dict[str, int]:
"""
Get durations for multiple audio files in parallel.
Args:
file_paths: List of file paths, or a single directory path string.
max_workers: Maximum number of worker processes. Defaults to CPU count.
Returns:
Dict[str, int]: Mapping of file_path -> duration_in_seconds.
Files that failed to parse are omitted.
Examples:
>>> durations = batch_get_duration(["song1.mp3", "song2.flac"])
>>> print(durations["song1.mp3"]) # 240
>>> durations = batch_get_duration("/music/playlist/")
"""
metadata_list = batch_get_metadata(
file_paths,
max_workers=max_workers,
show_progress=False
)
return {item["path"]: item["duration"] for item in metadata_list}
def batch_get_metadata_by_type(
file_paths: Union[List[str], str],
file_type: str,
max_workers: Optional[int] = None
) -> List[Dict]:
"""
Parse multiple audio files but only return results for a specific format.
Useful when you only care about MP3 files in a mixed directory.
Args:
file_paths: List of file paths, or a single directory path string.
file_type: File extension to filter (e.g., "mp3", "flac")
max_workers: Maximum number of worker processes.
Returns:
List[Dict]: Metadata for files matching the specified type.
"""
file_type = file_type.lower().lstrip(".")
all_results = batch_get_metadata(
file_paths,
max_workers=max_workers,
show_progress=False
)
return [r for r in all_results if r.get("format", "").lower() == file_type]
def get_audio_duration(file_path: str) -> int:
"""Get duration of a single audio file in seconds."""
try:
audio = open_audio(file_path)
return int(audio.length)
except Exception:
return 0
def get_audio_metadata(file_path: str) -> Optional[Dict]:
"""Get complete metadata for a single audio file."""
try:
audio = open_audio(file_path)
info = audio.info
ext = os.path.splitext(file_path)[1].lower().lstrip(".")
return {
"path": file_path,
"format": ext,
"duration": int(info.length),
"length": float(info.length),
"sample_rate": info.sample_rate,
"channels": info.channels,
"bitrate": info.bitrate,
}
except Exception:
return None
+1151
View File
File diff suppressed because it is too large Load Diff
+70
View File
@@ -0,0 +1,70 @@
from setuptools import setup, find_packages
import os
# 读取README.md
def read_file(filename):
try:
with open(filename, 'r', encoding='utf-8') as f:
return f.read()
except:
return "Audio Player By DVS - Advanced audio processing and playback library"
# 定义版本常量
VERSION = "4.0.1"
# 自动生成或更新 _version.py
def write_version_file():
version_file_path = os.path.join("ap_ds", "_version.py")
os.makedirs(os.path.dirname(version_file_path), exist_ok=True)
with open(version_file_path, "w", encoding="utf-8") as f:
f.write(f'__version__ = "{VERSION}"\n')
# 调用函数生成版本文件
write_version_file()
# 获取README内容
long_description = read_file("README.md")
setup(
name="ap_ds",
version=VERSION,
description="Audio Player By DVS - Advanced audio processing and playback",
long_description=long_description,
long_description_content_type="text/markdown",
author="DVS",
author_email="me@dvsyun.top",
url="https://apds.top",
packages=find_packages(),
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
# 自定义许可信息
"License :: Other/Proprietary License",
"Operating System :: Microsoft :: Windows",
"Operating System :: MacOS",
"Operating System :: POSIX :: Linux", # 仅保留Linux,删掉Unix
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Multimedia :: Sound/Audio",
"Topic :: Software Development :: Libraries :: Python Modules",
],
keywords="audio music player playback sdl2 dvs",
python_requires=">=3.7",
install_requires=[], # 纯Python依赖
include_package_data=True,
# 添加许可证信息 - 根据要求修改
license="Custom Open Source License",
# 确保包含所有必要的文件
package_data={
'': ['*.md', '*.txt', '*.py'], # 包含所有markdown和文本文件
},
# 添加项目URLs(删除了GitHub链接)
project_urls={
'Documentation': 'https://apds.top',
'License Info': 'https://apds.top',
},
)