Initial commit: ap_ds Code Repository System v1.0.0

This commit is contained in:
dvs
2026-08-28 11:23:52 +08:00
commit 74a93c63f2
23 changed files with 5811 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}404 - 页面未找到{% endblock %}
{% block content %}
<div class="repo-container repo-text-center repo-py-10">
<span style="font-size: 6rem;">🔍</span>
<h1 class="repo-text-2xl repo-font-bold repo-mt-4">404</h1>
<p class="repo-text-secondary repo-mt-2">页面未找到</p>
<a href="{{ url_for('index') }}" class="repo-btn repo-btn--primary repo-mt-6">返回首页</a>
</div>
{% endblock %}
+12
View File
@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}500 - 服务器错误{% endblock %}
{% block content %}
<div class="repo-container repo-text-center repo-py-10">
<span style="font-size: 6rem;">⚠️</span>
<h1 class="repo-text-2xl repo-font-bold repo-mt-4">500</h1>
<p class="repo-text-secondary repo-mt-2">服务器内部错误</p>
<a href="{{ url_for('index') }}" class="repo-btn repo-btn--primary repo-mt-6">返回首页</a>
</div>
{% endblock %}
+90
View File
@@ -0,0 +1,90 @@
{% extends "base.html" %}
{% block title %}分支管理 - ap_ds{% endblock %}
{% block content %}
<div class="repo-container">
<div class="repo-flex repo-justify-between repo-items-center repo-mb-6">
<h1 class="repo-text-2xl repo-font-bold">分支与版本管理</h1>
<a href="{{ url_for('admin_dashboard') }}" class="repo-btn repo-btn--ghost">返回仪表板</a>
</div>
<!-- 创建分支 -->
<div class="repo-card repo-mb-6">
<div class="repo-card__header">
<h3 class="repo-card__title">创建新分支</h3>
</div>
<div class="repo-card__body">
<form method="post" action="{{ url_for('admin_branch_create') }}" class="repo-flex repo-gap-3">
<input type="text" name="branch_name" class="repo-input" placeholder="分支名称 (如: LFV)" required>
<button type="submit" class="repo-btn repo-btn--primary">创建分支</button>
</form>
</div>
</div>
<!-- 上传新版本 -->
<div class="repo-card repo-mb-6">
<div class="repo-card__header">
<h3 class="repo-card__title">上传新版本</h3>
</div>
<div class="repo-card__body">
<form method="post" action="{{ url_for('admin_version_upload') }}" enctype="multipart/form-data">
<div class="repo-row">
<div class="repo-col">
<select name="branch" class="repo-select__trigger repo-w-full" required>
<option value="">选择分支</option>
{% for branch_name in branches.keys() %}
<option value="{{ branch_name }}">{{ branch_name }}</option>
{% endfor %}
</select>
</div>
<div class="repo-col">
<input type="text" name="version" class="repo-input" placeholder="版本号 (如: v2.5.0)" required>
</div>
<div class="repo-col">
<input type="file" name="files" class="repo-input" multiple>
</div>
<div class="repo-col">
<button type="submit" class="repo-btn repo-btn--primary">上传版本</button>
</div>
</div>
</form>
</div>
</div>
<!-- 分支列表 -->
{% for branch_name, versions in branches.items() %}
<div class="repo-card repo-mb-6">
<div class="repo-card__header">
<h3 class="repo-card__title">📁 {{ branch_name }}</h3>
</div>
<div class="repo-card__body">
<table class="repo-table">
<thead>
<tr>
<th>版本</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for version in versions %}
<tr>
<td>{{ version.name }}</td>
<td class="repo-text-secondary">{{ version.created }}</td>
<td>
<a href="{{ url_for('admin_version_delete', branch=branch_name, version=version.name) }}"
class="repo-btn repo-btn--sm repo-btn--danger"
onclick="return confirm('确定要删除版本 {{ version.name }} 吗?')">
删除
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
+117
View File
@@ -0,0 +1,117 @@
{% extends "base.html" %}
{% block title %}管理仪表板 - ap_ds{% endblock %}
{% block content %}
<div class="repo-container">
<div class="repo-flex repo-justify-between repo-items-center repo-mb-6">
<h1 class="repo-text-2xl repo-font-bold">管理仪表板</h1>
<a href="{{ url_for('admin_logout') }}" class="repo-btn repo-btn--danger">登出</a>
</div>
<!-- 统计卡片 -->
<div class="repo-row repo-mb-6">
<div class="repo-col">
<div class="repo-card">
<div class="repo-card__body repo-text-center">
<div class="repo-text-3xl repo-font-bold" style="color: var(--repo-color-primary);">{{ stats.branches }}</div>
<div class="repo-text-secondary">分支</div>
</div>
</div>
</div>
<div class="repo-col">
<div class="repo-card">
<div class="repo-card__body repo-text-center">
<div class="repo-text-3xl repo-font-bold" style="color: var(--repo-color-success);">{{ stats.versions }}</div>
<div class="repo-text-secondary">版本</div>
</div>
</div>
</div>
<div class="repo-col">
<div class="repo-card">
<div class="repo-card__body repo-text-center">
<div class="repo-text-3xl repo-font-bold" style="color: var(--repo-color-warning);">{{ stats.feedbacks }}</div>
<div class="repo-text-secondary">反馈总数</div>
</div>
</div>
</div>
<div class="repo-col">
<div class="repo-card">
<div class="repo-card__body repo-text-center">
<div class="repo-text-3xl repo-font-bold" style="color: var(--repo-color-danger);">{{ stats.unread_feedbacks }}</div>
<div class="repo-text-secondary">未读反馈</div>
</div>
</div>
</div>
</div>
<!-- 快速操作 -->
<div class="repo-row">
<div class="repo-col">
<div class="repo-card">
<div class="repo-card__header">
<h3 class="repo-card__title">上传 WHL 文件</h3>
</div>
<div class="repo-card__body">
<form method="post" action="{{ url_for('admin_whl_upload') }}" enctype="multipart/form-data">
<input type="file" name="whl_file" accept=".whl" class="repo-input repo-mb-3" required>
<button type="submit" class="repo-btn repo-btn--primary">上传</button>
</form>
</div>
</div>
</div>
<div class="repo-col">
<div class="repo-card">
<div class="repo-card__header">
<h3 class="repo-card__title">管理链接</h3>
</div>
<div class="repo-card__body">
<div class="repo-flex repo-gap-3">
<a href="{{ url_for('admin_branches') }}" class="repo-btn repo-btn--primary">分支管理</a>
<a href="{{ url_for('admin_feedbacks') }}" class="repo-btn repo-btn--secondary">反馈管理</a>
</div>
</div>
</div>
</div>
</div>
<!-- 最近反馈 -->
<div class="repo-card repo-mt-6">
<div class="repo-card__header">
<h3 class="repo-card__title">最近反馈</h3>
</div>
<div class="repo-card__body">
{% if recent_feedbacks %}
<table class="repo-table">
<thead>
<tr>
<th>时间</th>
<th>邮箱</th>
<th>内容</th>
<th>状态</th>
</tr>
</thead>
<tbody>
{% for f in recent_feedbacks %}
<tr>
<td class="repo-text-sm">{{ f.created }}</td>
<td class="repo-text-sm">{{ f.email }}</td>
<td class="repo-text-sm repo-truncate" style="max-width: 300px;">{{ f.content[:50] }}...</td>
<td>
{% if f.status == 'unread' %}
<span class="repo-tag repo-tag--danger">未读</span>
{% else %}
<span class="repo-tag repo-tag--default">已读</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="repo-text-secondary repo-text-center">暂无反馈</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}
+62
View File
@@ -0,0 +1,62 @@
{% extends "base.html" %}
{% block title %}反馈管理 - ap_ds{% endblock %}
{% block content %}
<div class="repo-container">
<div class="repo-flex repo-justify-between repo-items-center repo-mb-6">
<h1 class="repo-text-2xl repo-font-bold">反馈管理</h1>
<a href="{{ url_for('admin_dashboard') }}" class="repo-btn repo-btn--ghost">返回仪表板</a>
</div>
<div class="repo-card">
<div class="repo-card__body">
{% if feedbacks %}
<table class="repo-table">
<thead>
<tr>
<th>ID</th>
<th>时间</th>
<th>邮箱</th>
<th>内容</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for f in feedbacks|reverse %}
<tr>
<td>{{ f.id }}</td>
<td class="repo-text-sm">{{ f.created }}</td>
<td class="repo-text-sm">{{ f.email }}</td>
<td class="repo-text-sm" style="max-width: 400px;">
<div class="repo-line-clamp-2">{{ f.content }}</div>
</td>
<td>
{% if f.status == 'unread' %}
<span class="repo-tag repo-tag--danger">未读</span>
{% else %}
<span class="repo-tag repo-tag--default">已读</span>
{% endif %}
</td>
<td>
<div class="repo-flex repo-gap-2">
{% if f.status == 'unread' %}
<a href="{{ url_for('admin_feedback_mark_read', feedback_id=f.id) }}" class="repo-btn repo-btn--sm repo-btn--primary">标记已读</a>
{% endif %}
<a href="{{ url_for('admin_feedback_delete', feedback_id=f.id) }}"
class="repo-btn repo-btn--sm repo-btn--danger"
onclick="return confirm('确定要删除这条反馈吗?')">删除</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="repo-text-secondary repo-text-center">暂无反馈</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}
+30
View File
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理员登录 - ap_ds</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css.css') }}">
</head>
<body style="background: var(--repo-color-bg-secondary); min-height: 100vh; display: flex; align-items: center; justify-content: center;">
<div class="repo-card" style="width: 100%; max-width: 400px;">
<div class="repo-card__header">
<h2 class="repo-card__title">管理员登录</h2>
</div>
<div class="repo-card__body">
{% if error %}
<div class="repo-message repo-message--error" style="position: static; transform: none; margin-bottom: 1rem;">
{{ error }}
</div>
{% endif %}
<form method="post">
<div class="repo-mb-4">
<label class="repo-text-sm repo-font-medium repo-mb-2 repo-block">密码</label>
<input type="password" name="password" class="repo-input" required autofocus>
</div>
<button type="submit" class="repo-btn repo-btn--primary repo-w-full">登录</button>
</form>
</div>
</div>
</body>
</html>
+581
View File
@@ -0,0 +1,581 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}ap_ds · 轻量级Python音频库{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css.css') }}">
<!-- highlight.js 主题 (浅色) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
/* 浅色主题覆盖 - 基于 css.css 变量 */
:root {
--repo-color-primary: #3b82f6;
--repo-color-primary-hover: #2563eb;
--repo-color-text: #1f2937;
--repo-color-text-secondary: #4b5563;
--repo-color-text-tertiary: #9ca3af;
--repo-color-bg: #ffffff;
--repo-color-bg-secondary: #f9fafb;
--repo-color-bg-tertiary: #f3f4f6;
--repo-color-border: #e5e7eb;
--repo-color-border-hover: #d1d5db;
}
/* 全局样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--repo-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif);
font-size: var(--repo-font-size-base, 14px);
line-height: var(--repo-line-height-base, 1.5);
color: var(--repo-color-text);
background-color: var(--repo-color-bg-secondary);
}
/* 导航栏 - 使用 css.css 变量 */
.header {
background-color: var(--repo-color-bg);
border-bottom: 1px solid var(--repo-color-border);
position: sticky;
top: 0;
z-index: var(--repo-z-sticky, 100);
}
.header-inner {
max-width: 1280px;
margin: 0 auto;
padding: 0 var(--repo-spacing-6, 16px);
display: flex;
align-items: center;
justify-content: space-between;
height: var(--repo-header-height, 60px);
}
.logo-area {
display: flex;
align-items: center;
gap: var(--repo-spacing-4, 16px);
}
.logo {
font-size: var(--repo-font-size-xl, 18px);
font-weight: 600;
color: var(--repo-color-text);
text-decoration: none;
}
.logo span {
color: var(--repo-color-primary);
}
.nav-links {
display: flex;
gap: var(--repo-spacing-4, 16px);
}
.nav-links a {
color: var(--repo-color-text-secondary);
text-decoration: none;
font-size: var(--repo-font-size-base, 14px);
font-weight: 500;
padding: var(--repo-spacing-2, 8px) 0;
transition: color 0.2s ease;
}
.nav-links a:hover {
color: var(--repo-color-primary);
}
.repo-badge {
background-color: var(--repo-color-bg-tertiary);
padding: 4px 10px;
border-radius: var(--repo-radius-full, 20px);
font-size: var(--repo-font-size-xs, 12px);
font-family: var(--repo-font-family-mono, monospace);
color: var(--repo-color-text-secondary);
}
/* 主容器 */
.main-container {
max-width: 1280px;
margin: 0 auto;
padding: var(--repo-spacing-6, 24px) var(--repo-spacing-4, 16px);
}
/* 仓库头部 */
.repo-header {
margin-bottom: var(--repo-spacing-6, 24px);
}
.repo-path {
display: flex;
align-items: center;
gap: var(--repo-spacing-2, 8px);
font-size: var(--repo-font-size-xl, 18px);
font-weight: 600;
margin-bottom: var(--repo-spacing-4, 16px);
}
.repo-path a {
color: var(--repo-color-primary);
text-decoration: none;
}
.repo-path a:hover {
text-decoration: underline;
}
.repo-path .separator {
color: var(--repo-color-text-tertiary);
}
/* 两栏布局 */
.two-column {
display: flex;
gap: var(--repo-spacing-8, 32px);
}
/* 左侧边栏 */
.sidebar {
width: 260px;
flex-shrink: 0;
}
.sidebar-section {
margin-bottom: var(--repo-spacing-6, 24px);
}
.sidebar-title {
font-size: var(--repo-font-size-xs, 12px);
font-weight: 600;
color: var(--repo-color-text-tertiary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--repo-spacing-3, 12px);
}
.branch-selector {
background-color: var(--repo-color-bg);
border: 1px solid var(--repo-color-border);
border-radius: var(--repo-radius-md, 6px);
padding: var(--repo-spacing-2, 8px) var(--repo-spacing-3, 12px);
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
margin-bottom: var(--repo-spacing-4, 16px);
color: var(--repo-color-text);
}
.branch-selector:hover {
background-color: var(--repo-color-bg-hover);
border-color: var(--repo-color-border-hover);
}
/* 右侧主内容 */
.main-content {
flex: 1;
min-width: 0;
}
/* 文件列表表格 */
.file-list {
border: 1px solid var(--repo-color-border);
border-radius: var(--repo-radius-lg, 8px);
overflow: hidden;
}
.file-list-header {
background-color: var(--repo-color-bg-secondary);
padding: var(--repo-spacing-3, 12px) var(--repo-spacing-4, 16px);
border-bottom: 1px solid var(--repo-color-border);
display: flex;
justify-content: space-between;
font-size: var(--repo-font-size-xs, 12px);
color: var(--repo-color-text-tertiary);
font-weight: 500;
}
.file-row {
display: flex;
align-items: center;
padding: var(--repo-spacing-2, 8px) var(--repo-spacing-4, 16px);
border-bottom: 1px solid var(--repo-color-border);
transition: background 0.2s;
}
.file-row:hover {
background-color: var(--repo-color-bg-hover);
}
.file-row:last-child {
border-bottom: none;
}
.file-name {
flex: 1;
display: flex;
align-items: center;
gap: var(--repo-spacing-2, 8px);
}
.file-name a {
color: var(--repo-color-primary);
text-decoration: none;
font-family: var(--repo-font-family-mono, monospace);
font-size: var(--repo-font-size-sm, 13px);
}
.file-name a:hover {
text-decoration: underline;
}
.file-message {
flex: 2;
font-size: var(--repo-font-size-xs, 12px);
color: var(--repo-color-text-tertiary);
}
.file-time {
width: 100px;
font-size: var(--repo-font-size-xs, 12px);
color: var(--repo-color-text-tertiary);
text-align: right;
}
/* README 区域 */
.readme-section {
margin-top: var(--repo-spacing-8, 32px);
border: 1px solid var(--repo-color-border);
border-radius: var(--repo-radius-lg, 8px);
overflow: hidden;
}
.readme-header {
background-color: var(--repo-color-bg-secondary);
padding: var(--repo-spacing-3, 12px) var(--repo-spacing-4, 16px);
border-bottom: 1px solid var(--repo-color-border);
font-weight: 600;
font-size: var(--repo-font-size-base, 14px);
color: var(--repo-color-text);
}
.readme-content {
padding: var(--repo-spacing-8, 32px);
background-color: var(--repo-color-bg);
}
.readme-content h1, .readme-content h2, .readme-content h3 {
margin-top: var(--repo-spacing-6, 24px);
margin-bottom: var(--repo-spacing-4, 16px);
font-weight: 600;
}
.readme-content h1 { font-size: 2em; border-bottom: 1px solid var(--repo-color-border); padding-bottom: 0.3em; }
.readme-content h2 { font-size: 1.5em; border-bottom: 1px solid var(--repo-color-border); padding-bottom: 0.3em; }
.readme-content h3 { font-size: 1.25em; }
.readme-content p { margin-bottom: var(--repo-spacing-4, 16px); line-height: 1.6; }
.readme-content code {
background-color: var(--repo-color-bg-tertiary);
padding: 0.2em 0.4em;
border-radius: var(--repo-radius-sm, 4px);
font-family: var(--repo-font-family-mono, monospace);
font-size: 85%;
}
.readme-content pre {
background-color: var(--repo-color-bg-tertiary);
padding: var(--repo-spacing-4, 16px);
border-radius: var(--repo-radius-md, 6px);
overflow-x: auto;
margin-bottom: var(--repo-spacing-4, 16px);
}
.readme-content pre code {
background: none;
padding: 0;
}
.readme-content ul, .readme-content ol {
padding-left: 2em;
margin-bottom: var(--repo-spacing-4, 16px);
}
.readme-content blockquote {
border-left: 4px solid var(--repo-color-border);
padding-left: var(--repo-spacing-4, 16px);
color: var(--repo-color-text-tertiary);
margin-bottom: var(--repo-spacing-4, 16px);
}
.readme-content table {
border-collapse: collapse;
margin-bottom: var(--repo-spacing-4, 16px);
width: 100%;
}
.readme-content th, .readme-content td {
border: 1px solid var(--repo-color-border);
padding: var(--repo-spacing-2, 8px) var(--repo-spacing-3, 12px);
}
.readme-content th {
background-color: var(--repo-color-bg-secondary);
}
/* 代码查看器 */
.code-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--repo-spacing-3, 12px) var(--repo-spacing-4, 16px);
background-color: var(--repo-color-bg-secondary);
border: 1px solid var(--repo-color-border);
border-radius: var(--repo-radius-lg, 8px) var(--repo-radius-lg, 8px) 0 0;
}
.code-content {
border: 1px solid var(--repo-color-border);
border-top: none;
border-radius: 0 0 var(--repo-radius-lg, 8px) var(--repo-radius-lg, 8px);
overflow: auto;
}
.code-content pre {
margin: 0;
padding: var(--repo-spacing-4, 16px);
background-color: var(--repo-color-bg);
}
.code-content code {
font-family: var(--repo-font-family-mono, "SF Mono", Monaco, monospace);
font-size: var(--repo-font-size-sm, 13px);
line-height: 1.5;
}
/* 按钮 - 使用 css.css 样式 */
.copy-btn {
background-color: var(--repo-color-bg);
border: 1px solid var(--repo-color-border);
color: var(--repo-color-text-secondary);
padding: var(--repo-spacing-1, 4px) var(--repo-spacing-3, 12px);
border-radius: var(--repo-radius-md, 6px);
font-size: var(--repo-font-size-xs, 12px);
cursor: pointer;
transition: all 0.2s ease;
}
.copy-btn:hover {
background-color: var(--repo-color-bg-hover);
border-color: var(--repo-color-border-hover);
color: var(--repo-color-text);
}
.btn {
display: inline-flex;
align-items: center;
gap: var(--repo-spacing-1, 4px);
padding: var(--repo-spacing-1, 4px) var(--repo-spacing-3, 12px);
font-size: var(--repo-font-size-xs, 12px);
font-weight: 500;
border-radius: var(--repo-radius-md, 6px);
cursor: pointer;
text-decoration: none;
transition: all 0.2s ease;
}
.btn-primary {
background-color: var(--repo-color-primary);
border: 1px solid transparent;
color: white;
}
.btn-primary:hover {
background-color: var(--repo-color-primary-hover);
}
.btn-secondary {
background-color: var(--repo-color-bg);
border: 1px solid var(--repo-color-border);
color: var(--repo-color-text);
}
.btn-secondary:hover {
background-color: var(--repo-color-bg-hover);
border-color: var(--repo-color-border-hover);
}
/* 搜索框 */
.search-box {
display: flex;
gap: var(--repo-spacing-2, 8px);
margin-bottom: var(--repo-spacing-6, 24px);
}
.search-input {
flex: 1;
background-color: var(--repo-color-bg);
border: 1px solid var(--repo-color-border);
border-radius: var(--repo-radius-md, 6px);
padding: var(--repo-spacing-2, 8px) var(--repo-spacing-3, 12px);
color: var(--repo-color-text);
font-size: var(--repo-font-size-base, 14px);
transition: all 0.2s ease;
}
.search-input:focus {
outline: none;
border-color: var(--repo-color-primary);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* 卡片 */
.repo-card-custom {
background-color: var(--repo-color-bg);
border: 1px solid var(--repo-color-border);
border-radius: var(--repo-radius-lg, 8px);
overflow: hidden;
}
/* 响应式 */
@media (max-width: 768px) {
.two-column {
flex-direction: column;
}
.sidebar {
width: 100%;
}
.file-message {
display: none;
}
.header-inner {
padding: 0 var(--repo-spacing-4, 16px);
}
.nav-links {
gap: var(--repo-spacing-3, 12px);
}
.nav-links a {
font-size: var(--repo-font-size-sm, 13px);
}
}
/* 图标占位 */
.icon {
width: 16px;
text-align: center;
font-size: 14px;
}
/* 加载动画 */
.loading {
text-align: center;
padding: var(--repo-spacing-8, 32px);
color: var(--repo-color-text-tertiary);
}
/* 提示信息 */
.info-message {
background-color: var(--repo-color-bg-secondary);
border-left: 4px solid var(--repo-color-primary);
padding: var(--repo-spacing-4, 16px);
border-radius: var(--repo-radius-md, 6px);
margin-bottom: var(--repo-spacing-4, 16px);
}
.error-message {
background-color: #fef2f2;
border-left: 4px solid var(--repo-color-danger, #ef4444);
padding: var(--repo-spacing-4, 16px);
border-radius: var(--repo-radius-md, 6px);
margin-bottom: var(--repo-spacing-4, 16px);
color: #b91c1c;
}
.success-message {
background-color: #f0fdf4;
border-left: 4px solid var(--repo-color-success, #10b981);
padding: var(--repo-spacing-4, 16px);
border-radius: var(--repo-radius-md, 6px);
margin-bottom: var(--repo-spacing-4, 16px);
color: #047857;
}
/* 分支菜单下拉 */
.branch-menu-dropdown {
position: absolute;
background-color: var(--repo-color-bg);
border: 1px solid var(--repo-color-border);
border-radius: var(--repo-radius-md, 6px);
width: 200px;
z-index: var(--repo-z-dropdown, 100);
box-shadow: var(--repo-shadow-lg, 0 10px 15px -3px rgba(0,0,0,0.1));
}
.branch-menu-item {
padding: var(--repo-spacing-2, 8px) var(--repo-spacing-3, 12px);
cursor: pointer;
transition: background 0.2s;
}
.branch-menu-item:hover {
background-color: var(--repo-color-bg-hover);
}
</style>
{% block extra_head %}{% endblock %}
</head>
<body>
<div class="header">
<div class="header-inner">
<div class="logo-area">
<a href="/" class="logo">ap_<span>ds</span></a>
<div class="nav-links">
<a href="{{ url_for('index') }}">首页</a>
<a href="{{ url_for('code_browser') }}">代码</a>
<a href="{{ url_for('feedback') }}">提问/建议</a>
<a href="{{ url_for('releases') }}">发行版</a>
</div>
</div>
<div class="repo-badge">
v3.0.0
</div>
</div>
</div>
<div class="main-container">
{% block content %}{% endblock %}
</div>
<script>
// 初始化 highlight.js
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('pre code').forEach(function(block) {
hljs.highlightElement(block);
});
});
// 复制代码功能
function copyCode(btn) {
var pre = btn.closest('.code-header')?.nextElementSibling;
var codeBlock = pre?.querySelector('code');
if (!codeBlock) return;
var code = codeBlock.innerText;
navigator.clipboard.writeText(code).then(function() {
var originalText = btn.innerText;
btn.innerText = '已复制!';
setTimeout(function() { btn.innerText = originalText; }, 2000);
});
}
// 全局分支/版本选择辅助函数
function selectBranch(branch, baseUrl) {
window.location.href = baseUrl + '?branch=' + encodeURIComponent(branch);
}
function selectVersion(version, baseUrl, branch) {
window.location.href = baseUrl + '?branch=' + encodeURIComponent(branch) + '&version=' + encodeURIComponent(version);
}
</script>
{% block extra_scripts %}{% endblock %}
</body>
</html>
+381
View File
@@ -0,0 +1,381 @@
<!-- 英文 SEO 标签 -->
<meta name="description" content="ap_ds official code repository. Browse branches (APDSLTS001), tags (v3.0.0), view README/setup.py with syntax highlighting, search files, and download ZIP/TAR.GZ/WHL. Self-hosted, never offline.">
<meta name="keywords" content="ap_ds source code, Python audio library code, browse repository, download WHL, self-hosted git, APDSLTS001, v3.0.0 source">
<meta property="og:title" content="ap_ds Code Repository – Browse & Download Source">
<meta property="og:description" content="Official self-hosted Git-style code browser for ap_ds. Syntax-highlighted Python files, Markdown rendering, multi-format downloads.">
<meta property="og:type" content="software">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="ap_ds Code Repository">
<!-- 中文 SEO 标签 -->
<meta name="description" lang="zh-CN" content="ap_ds 官方代码仓库。浏览分支(APDSLTS001)和版本(v3.0.0)下的所有源文件,语法高亮预览 README/setup.py,文件搜索,一键下载 ZIP/TAR.GZ/WHL。自主托管,永不掉线。">
<meta name="keywords" lang="zh-CN" content="ap_ds 源代码, Python音频库代码, 浏览仓库, 下载WHL, 自建Git仓库, APDSLTS001, v3.0.0源码">
<meta property="og:title" lang="zh-CN" content="ap_ds 代码仓库 – 浏览与下载源码">
<meta property="og:description" lang="zh-CN" content="ap_ds 官方自建代码仓库。支持语法高亮的 Python 文件预览、Markdown 渲染、多格式源码下载。">{% extends "base.html" %}
{% block content %}
{% block title %}ap_ds Code Repository – Official Self-Hosted Git Source Code & Downloads{% endblock %}<!-- 仓库头部 -->
<div class="repo-header">
<div class="repo-path">
<a href="/code">ap_ds</a>
<span class="separator">/</span>
<span>{{ current_branch if current_branch else '选择分支' }}</span>
{% if current_version %}
<span class="separator">/</span>
<span>{{ current_version }}</span>
{% endif %}
</div>
</div>
<!-- 搜索栏 -->
<div class="search-box">
<form action="{{ url_for('search') }}" method="get" style="display: flex; gap: 8px; width: 100%;">
<input type="hidden" name="branch" value="{{ current_branch }}">
<input type="hidden" name="version" value="{{ current_version }}">
<input type="text" name="q" class="search-input" placeholder="查找文件..." value="{{ search_query or '' }}">
<button type="submit" class="btn btn-primary">搜索</button>
</form>
</div>
<div class="two-column">
<!-- 左侧边栏 -->
<div class="sidebar">
<div class="sidebar-section">
<div class="sidebar-title">📌 分支</div>
<div class="branch-selector" id="branch-selector">
<span>{{ current_branch if current_branch else '选择分支' }}</span>
<span>▼</span>
</div>
<div id="branch-menu" class="branch-menu-dropdown" style="display: none;">
{% for branch_name in branches.keys() %}
<div class="branch-menu-item" data-branch="{{ branch_name }}">{{ branch_name }}</div>
{% endfor %}
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-title">🏷️ 版本</div>
<div class="branch-selector" id="version-selector">
<span>{{ current_version if current_version else '选择版本' }}</span>
<span>▼</span>
</div>
<div id="version-menu" class="branch-menu-dropdown" style="display: none;">
{% if current_branch and branches[current_branch] %}
{% for version in branches[current_branch] %}
<div class="branch-menu-item" data-version="{{ version.name }}">{{ version.name }}</div>
{% endfor %}
{% endif %}
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-title">📦 下载</div>
<div style="display: flex; flex-direction: column; gap: 8px;">
<a href="{{ url_for('download_zip', branch=current_branch, version=current_version) if current_branch and current_version else '#' }}" class="btn btn-secondary {% if not current_branch or not current_version %}disabled{% endif %}" {% if not current_branch or not current_version %}style="opacity:0.5; pointer-events:none;"{% endif %}>
📥 下载 ZIP
</a>
<a href="{{ url_for('download_tar', branch=current_branch, version=current_version) if current_branch and current_version else '#' }}" class="btn btn-secondary {% if not current_branch or not current_version %}disabled{% endif %}" {% if not current_branch or not current_version %}style="opacity:0.5; pointer-events:none;"{% endif %}>
📥 下载 TAR.GZ
</a>
<a href="{{ url_for('download_whl', version=current_version) if current_version else '#' }}" class="btn btn-secondary {% if not current_version %}disabled{% endif %}" {% if not current_version %}style="opacity:0.5; pointer-events:none;"{% endif %}>
📥 下载 WHL
</a>
</div>
</div>
<div class="sidebar-section">
<div class="sidebar-title">🔗 快速链接</div>
<div style="display: flex; flex-direction: column; gap: 6px;">
<a href="/releases" class="btn btn-secondary" style="text-align: center;">查看发行版</a>
<a href="/feedback" class="btn btn-secondary" style="text-align: center;">提交反馈</a>
</div>
</div>
</div>
<!-- 右侧内容 -->
<div class="main-content">
<!-- 搜索结果 -->
{% if search_results %}
<div class="file-list">
<div class="file-list-header">
<span>🔍 搜索结果 ({{ search_results|length }})</span>
<span></span>
</div>
{% for result in search_results %}
<div class="file-row">
<div class="file-name">
<span class="icon">📄</span>
{% set relative_path = result.path.replace(current_branch + '/' + current_version + '/', '') %}
<a href="{{ url_for('view_file', file_path=relative_path, branch=current_branch, version=current_version) }}">{{ result.name }}</a>
</div>
<div class="file-message">{{ result.path }}</div>
<div class="file-time">
{% if result.size < 1024 %}
{{ result.size }} B
{% elif result.size < 1024*1024 %}
{{ (result.size/1024)|round(1) }} KB
{% else %}
{{ (result.size/(1024*1024))|round(1) }} MB
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- 文件列表 -->
{% if not search_results %}
<div class="file-list">
<div class="file-list-header">
<span>📁 文件列表</span>
<span></span>
<span>大小</span>
</div>
<!-- 文件列表中的链接改为使用完整路径 -->
{% for item in file_tree %}
<div class="file-row">
<div class="file-name">
<span class="icon">{% if item.type == 'dir' %}📁{% else %}📄{% endif %}</span>
{% if item.type == 'dir' %}
<a href="{{ url_for('browse_directory', dir_path=item.path, branch=current_branch, version=current_version) }}">{{ item.name }}</a>
{% else %}
<!-- 直接使用完整路径,不加替换 -->
<a href="{{ url_for('view_file', file_path=item.path, branch=current_branch, version=current_version) }}">{{ item.name }}</a>
{% endif %}
</div>
<div class="file-message">
{% if item.type == 'dir' %}目录{% else %}{{ item.name }}{% endif %}
</div>
<div class="file-time">
{% if item.type == 'file' %}
{% if item.size < 1024 %}
{{ item.size }} B
{% elif item.size < 1024*1024 %}
{{ (item.size/1024)|round(1) }} KB
{% else %}
{{ (item.size/(1024*1024))|round(1) }} MB
{% endif %}
{% else %}
-
{% endif %}
</div>
</div>
{% else %}
<div class="file-row">
<div class="file-name" style="justify-content: center; color: var(--repo-color-text-tertiary);">
此目录为空
</div>
<div class="file-message"></div>
<div class="file-time"></div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- README 区域 -->
<div class="readme-section">
<div class="readme-header">
📖 README.md
</div>
<div class="readme-content" id="readme-content">
<div class="loading">加载中...</div>
</div>
</div>
</div>
</div>
<script>
// 获取当前 URL 基础路径
var currentUrl = "{{ url_for('code_browser') }}";
var currentBranch = "{{ current_branch }}";
var currentVersion = "{{ current_version }}";
// ========== 分支/版本选择器 ==========
var branchSelector = document.getElementById('branch-selector');
var branchMenu = document.getElementById('branch-menu');
var versionSelector = document.getElementById('version-selector');
var versionMenu = document.getElementById('version-menu');
if (branchSelector) {
branchSelector.onclick = function(e) {
e.stopPropagation();
branchMenu.style.display = branchMenu.style.display === 'none' ? 'block' : 'none';
if (versionMenu) versionMenu.style.display = 'none';
};
}
if (versionSelector) {
versionSelector.onclick = function(e) {
e.stopPropagation();
versionMenu.style.display = versionMenu.style.display === 'none' ? 'block' : 'none';
if (branchMenu) branchMenu.style.display = 'none';
};
}
document.onclick = function() {
if (branchMenu) branchMenu.style.display = 'none';
if (versionMenu) versionMenu.style.display = 'none';
};
var branchItems = document.querySelectorAll('#branch-menu .branch-menu-item');
branchItems.forEach(function(item) {
item.onclick = function(e) {
e.stopPropagation();
var branch = this.getAttribute('data-branch');
if (branch) {
window.location.href = currentUrl + '?branch=' + encodeURIComponent(branch);
}
};
});
var versionItems = document.querySelectorAll('#version-menu .branch-menu-item');
versionItems.forEach(function(item) {
item.onclick = function(e) {
e.stopPropagation();
var version = this.getAttribute('data-version');
if (version && currentBranch) {
window.location.href = currentUrl + '?branch=' + encodeURIComponent(currentBranch) + '&version=' + encodeURIComponent(version);
}
};
});
// ========== 加载 README ==========
function loadReadme() {
if (!currentBranch || !currentVersion) {
document.getElementById('readme-content').innerHTML = '<div class="info-message" style="text-align: center;">请选择分支和版本以查看 README</div>';
return;
}
var readmeUrl = '/view/' + currentBranch + '/' + currentVersion + '/README.md?branch=' + currentBranch + '&version=' + currentVersion;
fetch(readmeUrl)
.then(function(res) {
if (res.ok) {
return res.text();
}
throw new Error('README.md 不存在');
})
.then(function(html) {
var container = document.getElementById('readme-content');
if (html.indexOf('<div class="readme-content">') !== -1) {
var match = html.match(/<div class="readme-content">([\s\S]*?)<\/div>/);
if (match && match[1]) {
container.innerHTML = match[1];
} else {
container.innerHTML = html;
}
} else {
container.innerHTML = html;
}
var codeBlocks = container.querySelectorAll('pre code');
codeBlocks.forEach(function(block) {
if (typeof hljs !== 'undefined') {
hljs.highlightElement(block);
}
});
})
.catch(function(err) {
document.getElementById('readme-content').innerHTML = '<div class="info-message" style="text-align: center;">📄 暂无 README.md 文件</div>';
});
}
loadReadme();
</script>
<style>
.disabled {
opacity: 0.5;
pointer-events: none;
cursor: not-allowed;
}
.branch-menu-dropdown {
position: absolute;
background-color: var(--repo-color-bg);
border: 1px solid var(--repo-color-border);
border-radius: var(--repo-radius-md, 6px);
width: 200px;
z-index: var(--repo-z-dropdown, 100);
box-shadow: var(--repo-shadow-md);
max-height: 300px;
overflow-y: auto;
}
.branch-menu-item {
padding: var(--repo-spacing-2, 8px) var(--repo-spacing-3, 12px);
cursor: pointer;
transition: background 0.2s;
color: var(--repo-color-text);
}
.branch-menu-item:hover {
background-color: var(--repo-color-bg-hover);
}
.loading {
text-align: center;
padding: var(--repo-spacing-8, 32px);
color: var(--repo-color-text-tertiary);
}
.info-message {
text-align: center;
padding: var(--repo-spacing-8, 32px);
color: var(--repo-color-text-tertiary);
}
/* README 内容样式 */
#readme-content h1, #readme-content h2, #readme-content h3 {
margin-top: 24px;
margin-bottom: 16px;
font-weight: 600;
}
#readme-content h1 { font-size: 2em; border-bottom: 1px solid var(--repo-color-border); padding-bottom: 0.3em; }
#readme-content h2 { font-size: 1.5em; border-bottom: 1px solid var(--repo-color-border); padding-bottom: 0.3em; }
#readme-content p { margin-bottom: 16px; line-height: 1.6; }
#readme-content code {
background-color: var(--repo-color-bg-tertiary);
padding: 0.2em 0.4em;
border-radius: 4px;
font-family: monospace;
}
#readme-content pre {
background-color: var(--repo-color-bg-tertiary);
padding: 16px;
border-radius: 8px;
overflow-x: auto;
margin-bottom: 16px;
}
#readme-content pre code {
background: none;
padding: 0;
}
#readme-content ul, #readme-content ol {
padding-left: 2em;
margin-bottom: 16px;
}
#readme-content blockquote {
border-left: 4px solid var(--repo-color-border);
padding-left: 16px;
color: var(--repo-color-text-tertiary);
margin-bottom: 16px;
}
#readme-content table {
border-collapse: collapse;
margin-bottom: 16px;
width: 100%;
}
#readme-content th, #readme-content td {
border: 1px solid var(--repo-color-border);
padding: 8px 12px;
}
#readme-content th {
background-color: var(--repo-color-bg-secondary);
}
</style>
{% endblock %}
+81
View File
@@ -0,0 +1,81 @@
{% extends "base.html" %}
{% block title %}目录浏览 - ap_ds{% endblock %}
{% block content %}
<div class="repo-container">
<!-- 面包屑导航 -->
<div class="repo-breadcrumb repo-mb-4">
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}" class="repo-breadcrumb__link">根目录</a>
{% for crumb in breadcrumbs %}
<span class="repo-breadcrumb__separator">/</span>
<a href="{{ url_for('browse_directory', dir_path=crumb.path, branch=branch, version=version) }}" class="repo-breadcrumb__link">
{{ crumb.name }}
</a>
{% endfor %}
</div>
<div class="repo-card">
<div class="repo-card__body">
<table class="repo-table">
<thead>
<tr>
<th>名称</th>
<th>大小</th>
<th>修改时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% if current_path %}
<tr>
<td>
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}" class="repo-text-link">..</a>
</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
{% endif %}
{% for file in files %}
<tr>
<td>
{% if file.type == 'dir' %}
<span class="icon-folder repo-mr-2"></span>
<a href="{{ url_for('browse_directory', dir_path=file.path, branch=branch, version=version) }}" class="repo-text-link">
{{ file.name }}
</a>
{% else %}
<span class="icon-file repo-mr-2"></span>
<a href="{{ url_for('view_file', file_path=file.path, branch=branch, version=version) }}" class="repo-text-link">
{{ file.name }}
</a>
{% endif %}
</td>
<td class="repo-text-secondary repo-text-sm">
{% if file.type == 'file' %}
{% if file.size < 1024 %}
{{ file.size }} B
{% elif file.size < 1024*1024 %}
{{ (file.size/1024)|round(1) }} KB
{% else %}
{{ (file.size/(1024*1024))|round(1) }} MB
{% endif %}
{% else %}
-
{% endif %}
</td>
<td class="repo-text-secondary repo-text-sm">{{ file.modified }}</td>
<td>
{% if file.type == 'file' %}
<a href="{{ url_for('download_file', file_path=file.path) }}" class="repo-btn repo-btn--sm repo-btn--ghost">下载</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
+77
View File
@@ -0,0 +1,77 @@
<!-- 英文 SEO 标签 -->
<meta name="description" content="Official feedback channel for ap_ds. Submit bug reports, feature requests, or general inquiries via form. Includes FAQ: supported formats (MP3/FLAC/OGG/WAV), DAP playback logs. Direct email replies from maintainer.">
<meta name="keywords" content="ap_ds feedback, bug report Python audio, feature request, ask question, DvsXT contact, MP3 FLAC OGG WAV support, Python audio library help">
<meta property="og:title" content="ap_ds Feedback – Ask Questions & Report Issues">
<meta property="og:description" content="Direct line to ap_ds maintainer. Submit feedback, get answers via email. Read FAQ before posting.">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="ap_ds Feedback Form">
<!-- 中文 SEO 标签 -->
<meta name="description" lang="zh-CN" content="ap_ds 官方反馈通道。提交 Bug 报告、功能建议或一般咨询。附常见问题:支持的音频格式(MP3/FLAC/OGG/WAV)、DAP 播放记录获取方法。维护者通过邮件直接回复。">
<meta name="keywords" lang="zh-CN" content="ap_ds 反馈, 提交Bug, 功能建议, 提问, DvsXT 联系, MP3 FLAC OGG WAV支持, Python音频库帮助">
<meta property="og:title" lang="zh-CN" content="ap_ds 反馈 – 提问与建议">
<meta property="og:description" lang="zh-CN" content="直连 ap_ds 维护者。提交反馈,邮件获取回复。提交前可查阅常见问题。">{% extends "base.html" %}
{% block content %}{% block title %}ap_ds Feedback – Submit Questions, Bug Reports & Feature Requests{% endblock %}
<div class="repo-container" style="max-width: 800px;">
<div class="repo-card">
<div class="repo-card__header">
<h2 class="repo-card__title">💬 提问与建议</h2>
<p class="repo-text-secondary repo-text-sm repo-mt-2">
欢迎向我们提出任何问题或建议,我们会认真对待每一条反馈。
</p>
</div>
<div class="repo-card__body">
{% if error %}
<div class="repo-message repo-message--error" style="position: static; transform: none; margin-bottom: 1rem;">
{{ error }}
</div>
{% endif %}
{% if success %}
<div class="repo-message repo-message--success" style="position: static; transform: none; margin-bottom: 1rem;">
{{ success }}
</div>
{% endif %}
<form method="post">
<div class="repo-mb-4">
<label class="repo-text-sm repo-font-medium repo-mb-2 repo-block">邮箱地址</label>
<input type="email" name="email" class="repo-input" required placeholder="your@email.com">
<p class="repo-text-xs repo-text-tertiary repo-mt-1">我们会将回复发送到此邮箱</p>
</div>
<div class="repo-mb-4">
<label class="repo-text-sm repo-font-medium repo-mb-2 repo-block">反馈内容</label>
<textarea name="content" class="repo-textarea repo-input" rows="8" required placeholder="请详细描述您的问题或建议..."></textarea>
</div>
<div class="repo-flex repo-gap-3">
<button type="submit" class="repo-btn repo-btn--primary">提交反馈</button>
<button type="reset" class="repo-btn repo-btn--secondary">清空</button>
</div>
</form>
</div>
</div>
<div class="repo-card repo-mt-6">
<div class="repo-card__body">
<h3 class="repo-font-semibold repo-mb-3">常见问题</h3>
<div class="repo-space-y-3">
<div>
<div class="repo-font-medium">Q: ap_ds 支持哪些音频格式?</div>
<p class="repo-text-secondary repo-text-sm">A: 支持 MP3、FLAC、OGG、WAV 四种主流格式。</p>
</div>
<div>
<div class="repo-font-medium">Q: 遇到 SSL 证书错误怎么办?</div>
<p class="repo-text-secondary repo-text-sm">A: ap_ds 内置了多层下载策略,会自动处理 SSL 问题。如仍遇到问题,请手动下载 SDL2 库。</p>
</div>
<div>
<div class="repo-font-medium">Q: 如何获取 DAP 播放记录?</div>
<p class="repo-text-secondary repo-text-sm">A: 使用 get_dap_recordings() 方法获取,或使用 save_dap_to_json() 保存为文件。</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+679
View File
@@ -0,0 +1,679 @@
<!-- 英文 SEO 标签 -->
<meta name="description" content="ap_ds is an ultra-lightweight Python audio library (2.5MB) for high-quality playback & precise metadata parsing of MP3, FLAC, OGG, WAV. Zero external dependencies, non-blocking architecture. Created by Dvs (DvsXT), released 2026-03-01. A precise revolution in Python audio.">
<meta name="keywords" content="Python audio library, lightweight audio player, MP3, FLAC, OGG, WAV metadata, non-blocking playback, ap_ds, DvsXT, zero dependencies, 2026 Python ecosystem">
<meta property="og:title" content="ap_ds: Ultra-Lightweight Python Audio Library (2.5MB)">
<meta property="og:description" content="Zero external deps, non-blocking playback, precise metadata parsing for MP3/FLAC/OGG/WAV. Released 2026-03-01 by Dvs (DvsXT).">
<meta property="og:type" content="software">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="ap_ds - Python Audio Library, 2.5MB">
<meta name="twitter:description" content="Solve the lightweight vs completeness contradiction in Python audio. Non-blocking, zero deps, 4 major formats.">
<!-- 中文 SEO 标签 -->
<meta name="description" lang="zh-CN" content="ap_ds 是一个超轻量级 Python 音频库(仅 2.5MB),支持 MP3、FLAC、OGG、WAV 四种主流格式的高质量播放和精确元数据解析。零外部 Python 依赖,非阻塞播放架构让 GUI 应用流畅运行。由 Dvs (DvsXT) 创作,发布于 2026-03-01,是 Python 音频领域的精准革命。">
<meta name="keywords" lang="zh-CN" content="Python音频库, 轻量级音频播放, MP3, FLAC, OGG, WAV元数据解析, 非阻塞播放, ap_ds, DvsXT, 零依赖, 2026 Python生态">
<meta property="og:title" content="ap_ds:超轻量级 Python 音频库 (2.5MB)">
<meta property="og:description" lang="zh-CN" content="零外部依赖,非阻塞播放,精准解析 MP3/FLAC/OGG/WAV。2026-03-01 由 Dvs (DvsXT) 发布。终结 Python 音频库的「完整性与轻量化」矛盾。">
<!-- 新增:官方自建代码仓库说明 -->
<meta name="description" content="This domain is the official self-hosted code repository for ap_ds. Direct source, stable releases, and authoritative documentation.">
<meta name="description" lang="zh-CN" content="本域名是 ap_ds 官方自建代码仓库。提供直接源代码、稳定版本及权威文档。"> {% extends "base.html" %}
{% block title %}ap_ds · lightweight Python audio solution ap_ds · 轻量级Python音频库{% endblock %}
{% block extra_head %}
<link rel="canonical" href="https://apds.top" />
<link rel="me" href="https://gitee.com/dssxt/ap_ds" />
<link rel="me" href="https://gitcode.com/dvsxt/ap_ds" />
<link rel="nofollow" href="https://github.com/dvs-web/ap_ds" />
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css">
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-python.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-bash.min.js"></script>
<style>
/* 首页自定义样式 - 基于 css.css 变量 */
.home-container {
max-width: 1280px;
margin: 0 auto;
padding: var(--repo-spacing-6) var(--repo-spacing-4);
}
/* Hero 区域 */
.hero-section {
text-align: center;
padding: var(--repo-spacing-10) 0 var(--repo-spacing-8);
background: linear-gradient(135deg, var(--repo-color-bg) 0%, var(--repo-color-bg-secondary) 100%);
border-radius: var(--repo-radius-xl);
margin-bottom: var(--repo-spacing-8);
}
.hero-title {
font-size: 3rem;
font-weight: 700;
margin-bottom: var(--repo-spacing-4);
background: linear-gradient(135deg, var(--repo-color-primary) 0%, #6366f1 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.hero-subtitle {
font-size: var(--repo-font-size-xl);
color: var(--repo-color-text-secondary);
margin-bottom: var(--repo-spacing-6);
}
.version-badges {
display: flex;
justify-content: center;
gap: var(--repo-spacing-3);
flex-wrap: wrap;
margin-top: var(--repo-spacing-4);
}
/* 目录 */
.toc-section {
background: var(--repo-color-bg-secondary);
border-radius: var(--repo-radius-lg);
padding: var(--repo-spacing-6);
margin-bottom: var(--repo-spacing-8);
border-left: 4px solid var(--repo-color-primary);
}
.toc-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: var(--repo-spacing-2);
margin-top: var(--repo-spacing-4);
}
.toc-link {
color: var(--repo-color-text-secondary);
text-decoration: none;
font-size: var(--repo-font-size-sm);
padding: var(--repo-spacing-1) 0;
display: inline-block;
}
.toc-link:hover {
color: var(--repo-color-primary);
text-decoration: none;
}
/* 内容区块 */
.content-card {
background: var(--repo-color-bg);
border: 1px solid var(--repo-color-border);
border-radius: var(--repo-radius-lg);
padding: var(--repo-spacing-8);
margin-bottom: var(--repo-spacing-8);
box-shadow: var(--repo-shadow-sm);
}
.content-card h2 {
font-size: var(--repo-font-size-2xl);
font-weight: 600;
margin-bottom: var(--repo-spacing-6);
padding-bottom: var(--repo-spacing-3);
border-bottom: 2px solid var(--repo-color-border);
color: var(--repo-color-text);
}
.content-card h3 {
font-size: var(--repo-font-size-xl);
font-weight: 600;
margin-top: var(--repo-spacing-6);
margin-bottom: var(--repo-spacing-4);
color: var(--repo-color-text);
}
.content-card h4 {
font-size: var(--repo-font-size-lg);
font-weight: 600;
margin-top: var(--repo-spacing-4);
margin-bottom: var(--repo-spacing-3);
color: var(--repo-color-text);
}
/* 特性网格 */
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: var(--repo-spacing-5);
margin-bottom: var(--repo-spacing-8);
}
.feature-card {
background: var(--repo-color-bg-secondary);
border-radius: var(--repo-radius-lg);
padding: var(--repo-spacing-5);
border: 1px solid var(--repo-color-border);
transition: all 0.2s ease;
}
.feature-card:hover {
transform: translateY(-2px);
box-shadow: var(--repo-shadow-md);
border-color: var(--repo-color-primary);
}
.feature-icon {
font-size: 2rem;
margin-bottom: var(--repo-spacing-3);
}
.feature-title {
font-weight: 600;
font-size: var(--repo-font-size-lg);
margin-bottom: var(--repo-spacing-2);
color: var(--repo-color-text);
}
.feature-desc {
color: var(--repo-color-text-secondary);
font-size: var(--repo-font-size-sm);
}
/* 对比表格 */
.comparison-table {
width: 100%;
border-collapse: collapse;
margin: var(--repo-spacing-4) 0;
}
.comparison-table th,
.comparison-table td {
border: 1px solid var(--repo-color-border);
padding: var(--repo-spacing-3) var(--repo-spacing-4);
text-align: left;
}
.comparison-table th {
background: var(--repo-color-bg-secondary);
font-weight: 600;
}
/* 提示框 */
.note-box {
background: #eef2ff;
border-left: 4px solid var(--repo-color-primary);
border-radius: var(--repo-radius-md);
padding: var(--repo-spacing-4) var(--repo-spacing-6);
margin: var(--repo-spacing-4) 0;
color: #1e40af;
}
.warning-box {
background: #fffbeb;
border-left: 4px solid #f59e0b;
border-radius: var(--repo-radius-md);
padding: var(--repo-spacing-4) var(--repo-spacing-6);
margin: var(--repo-spacing-4) 0;
color: #92400e;
}
.success-box {
background: #ecfdf5;
border-left: 4px solid #10b981;
border-radius: var(--repo-radius-md);
padding: var(--repo-spacing-4) var(--repo-spacing-6);
margin: var(--repo-spacing-4) 0;
color: #065f46;
}
/* 代码块 */
.code-block {
position: relative;
background: #0f172a;
border-radius: var(--repo-radius-lg);
margin: var(--repo-spacing-4) 0;
overflow: hidden;
}
.code-block pre {
margin: 0;
padding: var(--repo-spacing-5);
overflow-x: auto;
background: transparent !important;
}
.code-block code {
font-family: var(--repo-font-family-mono);
font-size: var(--repo-font-size-sm);
color: #f8fafc;
}
.copy-btn {
position: absolute;
top: 12px;
right: 16px;
background: #334155;
color: #f1f5f9;
border: none;
border-radius: var(--repo-radius-full);
padding: 4px 14px;
font-size: var(--repo-font-size-xs);
cursor: pointer;
transition: 0.2s;
opacity: 0.7;
}
.copy-btn:hover {
opacity: 1;
background: #475569;
}
.copy-btn.copied {
background: #059669;
}
/* 许可证区域 */
.license-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: var(--repo-spacing-4);
margin-top: var(--repo-spacing-6);
}
.license-card {
background: var(--repo-color-bg-secondary);
border-radius: var(--repo-radius-lg);
padding: var(--repo-spacing-5);
border: 1px solid var(--repo-color-border);
}
.license-card h4 {
margin-top: 0;
margin-bottom: var(--repo-spacing-3);
}
/* 公告区域 */
.announcement-content {
line-height: 1.7;
}
.announcement-content h1,
.announcement-content h2,
.announcement-content h3 {
margin-top: var(--repo-spacing-5);
margin-bottom: var(--repo-spacing-3);
}
.announcement-content h1 { font-size: var(--repo-font-size-xl); border-bottom: 1px solid var(--repo-color-border); padding-bottom: var(--repo-spacing-2); }
.announcement-content h2 { font-size: var(--repo-font-size-lg); border-bottom: 1px solid var(--repo-color-border); padding-bottom: var(--repo-spacing-2); }
.announcement-content h3 { font-size: var(--repo-font-size-base); }
.announcement-content p { margin-bottom: var(--repo-spacing-4); }
.announcement-content ul, .announcement-content ol { padding-left: var(--repo-spacing-6); margin-bottom: var(--repo-spacing-4); }
.announcement-content code {
background: var(--repo-color-bg-tertiary);
padding: 0.2em 0.4em;
border-radius: var(--repo-radius-sm);
font-size: 0.9em;
}
.announcement-content pre {
background: #0f172a;
padding: var(--repo-spacing-4);
border-radius: var(--repo-radius-md);
overflow-x: auto;
margin: var(--repo-spacing-4) 0;
}
.announcement-content pre code {
background: none;
padding: 0;
color: #f8fafc;
}
.announcement-content blockquote {
border-left: 4px solid var(--repo-color-border);
padding-left: var(--repo-spacing-4);
margin: var(--repo-spacing-4) 0;
color: var(--repo-color-text-secondary);
}
/* 响应式 */
@media (max-width: 768px) {
.hero-title {
font-size: 2rem;
}
.content-card {
padding: var(--repo-spacing-5);
}
.feature-grid {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}
{% block content %}
<div class="home-container">
<!-- Hero 区域 -->
<div class="hero-section">
<h1 class="hero-title" id="hero-title">ap_ds: 轻量级 Python 音频革命</h1>
<p class="hero-subtitle" id="hero-subtitle">2.5MB 终结 160MB 臃肿时代</p>
<div class="version-badges" id="version-badges">
<span class="repo-tag repo-tag--primary">v3.0.0 LTS</span>
<span class="repo-tag repo-tag--success">仅 2.5MB</span>
<span class="repo-tag repo-tag--warning">跨平台 W/M/L</span>
</div>
</div>
<!-- 目录 -->
<div class="toc-section">
<h3 class="repo-text-lg repo-font-semibold repo-mb-4"><i class="fa fa-list-ul" style="margin-right: 8px; color: var(--repo-color-primary);"></i>目录</h3>
<div class="toc-grid">
<a href="#introduction" class="toc-link">1. 项目介绍</a>
<a href="#features" class="toc-link">2. 核心特性</a>
<a href="#comparison" class="toc-link">3. 技术对比</a>
<a href="#installation" class="toc-link">4. 安装与使用</a>
<a href="#license" class="toc-link">5. 开源许可证</a>
<a href="#faq" class="toc-link">6. 常见问题</a>
<a href="#contact" class="toc-link">7. 联系与支持</a>
<a href="#announcement" class="toc-link">8. 最新公告</a>
</div>
</div>
<!-- 1. 项目介绍 -->
<div class="content-card" id="introduction">
<h2>1. 项目介绍</h2>
<div class="note-box">
<p><strong>📋 官方信息</strong></p>
<p>项目名称:ap_ds</p>
<p>官方网站:<a href="https://apds.top" style="color: var(--repo-color-primary);">https://apds.top</a></p>
<p>PyPI:<a href="https://pypi.org/project/ap_ds/" style="color: var(--repo-color-primary);">https://pypi.org/project/ap_ds/</a></p>
<p>开发者:Dvs (DvsXT)</p>
</div>
<p>ap_ds 是一个极轻量级(2.5MB)的 Python 音频库,用于播放和精确解析 MP3、FLAC、OGG 和 WAV 文件。它没有外部 Python 依赖,仅使用 Python 标准库,为非阻塞播放提供平滑的 GUI 应用程序体验。</p>
<div class="success-box">
<p><strong>🎉 v3.0.0 LTS 长期支持版本</strong></p>
<p>首个长期支持版本,提供 <strong>永久免费技术支持</strong>,哈希验证下载,确定性资源清理。</p>
</div>
</div>
<!-- 2. 核心特性 -->
<div class="content-card" id="features">
<h2>2. 核心特性</h2>
<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon">⚡</div>
<div class="feature-title">极致轻量</div>
<div class="feature-desc">Windows 2.5MB · macOS 3.36MB · Linux 就绪</div>
</div>
<div class="feature-card">
<div class="feature-icon">🎵</div>
<div class="feature-title">四种格式</div>
<div class="feature-desc">MP3 / FLAC / OGG / WAV</div>
</div>
<div class="feature-card">
<div class="feature-icon">📊</div>
<div class="feature-title">精确元数据</div>
<div class="feature-desc">WAV/FLAC 100% 准确</div>
</div>
<div class="feature-card">
<div class="feature-icon">▶️</div>
<div class="feature-title">非阻塞播放</div>
<div class="feature-desc">完美适配 GUI 应用</div>
</div>
</div>
<div class="grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--repo-spacing-8);">
<div>
<h3>📦 体积对比</h3>
<ul style="list-style: none; padding-left: 0;">
<li style="margin-bottom: var(--repo-spacing-2);">🟢 <strong>ap_ds</strong> Windows: 2.5MB</li>
<li style="margin-bottom: var(--repo-spacing-2);">🍎 macOS: 3.36MB</li>
<li style="margin-bottom: var(--repo-spacing-2);">🐧 Linux: 动态链接 ~50KB</li>
<li style="margin-bottom: var(--repo-spacing-2);">🔴 FFmpeg: ≥160MB</li>
<li>🟡 Pygame + 解析库: 臃肿且功能不全</li>
</ul>
</div>
<div>
<h3>⚙️ 为什么这么小?</h3>
<ul>
<li>专注:仅播放和解析,无编辑/转码</li>
<li>基于工业级 SDL2</li>
<li>逐字节优化</li>
<li>跨平台 W/M/L</li>
</ul>
</div>
</div>
</div>
<!-- 3. 技术对比 -->
<div class="content-card" id="comparison">
<h2>3. 技术对比</h2>
<h3>第一章:Pygame —— 游戏引擎的"音频残疾"</h3>
<div class="code-block">
<pre class="language-python"><code># Pygame 不可靠的音频时长
my_sound = pygame.mixer.Sound('my_song.mp3')
total_length = my_sound.get_length() # 仅对 WAV 可靠!</code></pre>
<button class="copy-btn" onclick="copyCode(this)"><i class="fa fa-clipboard"></i> 复制</button>
</div>
<div class="warning-box">
⚠️ Pygame 的 <code>Sound.get_length()</code> 仅对 WAV 格式正确,MP3/OGG/FLAC 返回无效值。
</div>
<h3>第二章:FFmpeg 套件 —— 160MB 的"至少"之痛</h3>
<table class="comparison-table">
<thead><tr><th>组件</th><th>功能</th><th>大小</th></tr></thead>
<tbody>
<tr><td>ffplay.exe</td><td>播放核心</td><td>≈80MB</td></tr>
<tr><td>ffprobe.exe</td><td>元数据探测</td><td>≈80MB</td></tr>
<tr><td>ffmpeg.exe</td><td>转换器</td><td>≈80MB</td></tr>
</tbody>
</table>
<div class="note-box">🔍 三个独立可执行文件,无 Python 封装,总计 ≥160MB。</div>
<h3>第三章:ap_ds 革命</h3>
<div class="success-box">
<p><strong>七大技术突破</strong></p>
<ul>
<li>✓ 2.5MB 解决 160MB 问题</li>
<li>✓ 非阻塞消除 GUI 卡顿</li>
<li>✓ 精确进度查询</li>
<li>✓ 零依赖部署</li>
<li>✓ AID 多音频管理</li>
<li>✓ SDL2 性能</li>
<li>✓ 智能 C 依赖跨平台</li>
</ul>
</div>
</div>
<!-- 4. 安装与使用 -->
<div class="content-card" id="installation">
<h2>4. 安装与使用</h2>
<div class="code-block">
<pre class="language-bash"><code>pip install ap_ds</code></pre>
<button class="copy-btn" onclick="copyCode(this)"><i class="fa fa-clipboard"></i> 复制</button>
</div>
<h3>基础示例</h3>
<div class="code-block">
<pre class="language-python"><code>from ap_ds import AudioLibrary
lib = AudioLibrary()
aid = lib.play_from_file("music.mp3")
lib.pause_audio(aid)
lib.seek_audio(aid, 30.5)
duration = lib.stop_audio(aid)</code></pre>
<button class="copy-btn" onclick="copyCode(this)"><i class="fa fa-clipboard"></i> 复制</button>
</div>
<h3>DAP 播放列表 (v2.3.0+)</h3>
<div class="code-block">
<pre class="language-python"><code>aid1 = lib.play_from_file("song1.mp3")
recordings = lib.get_dap_recordings()
lib.save_dap_to_json("my_playlist.ap-ds-dap")</code></pre>
<button class="copy-btn" onclick="copyCode(this)"><i class="fa fa-clipboard"></i> 复制</button>
</div>
<h3>淡入淡出效果 (v2.4.0+)</h3>
<div class="code-block">
<pre class="language-python"><code># 2秒淡入
lib.fadein_music(aid, loops=-1, ms=2000)
# 3秒淡出
lib.fadeout_music(ms=3000)
# 检查播放状态
if lib.is_music_playing():
print("音乐播放中")</code></pre>
<button class="copy-btn" onclick="copyCode(this)"><i class="fa fa-clipboard"></i> 复制</button>
</div>
</div>
<!-- 5. 开源许可证 -->
<div class="content-card" id="license">
<h2>5. 开源许可证</h2>
<div class="license-grid">
<div class="license-card">
<h4>✅ 您可以</h4>
<ul>
<li>商业使用</li>
<li>修改代码</li>
<li>闭源集成</li>
<li>销售解决方案</li>
<li>云服务部署</li>
</ul>
</div>
<div class="license-card">
<h4>❌ 您不可以</h4>
<ul>
<li>使用原始品牌名称</li>
<li>删除版权声明</li>
<li>用于非法用途</li>
<li>专利侵权诉讼</li>
</ul>
</div>
<div class="license-card">
<h4>👍 您应该</h4>
<ul>
<li>保留版权声明</li>
<li>标注来源归属</li>
<li>报告安全问题</li>
<li>修改版本用独立品牌</li>
</ul>
</div>
</div>
<p class="repo-text-secondary repo-text-sm repo-mt-4">详细许可证请查看 <code>AP_DS_LICENSE.MD</code> 文件。</p>
</div>
<!-- 6. 常见问题 -->
<div class="content-card" id="faq">
<h2>6. 常见问题</h2>
<div><p class="repo-font-semibold">问:公司可以将其集成到商业产品中吗?</p><div class="success-box">✅ 完全可以,无需付费、无需通知、无需授权。</div></div>
<div><p class="repo-font-semibold repo-mt-4">问:我可以修改并重新分发吗?</p><div class="success-box">✅ 可以,但必须使用独立品牌、保留版权、标注来源、提供维护者声明。</div></div>
<div><p class="repo-font-semibold repo-mt-4">问:我必须开源我的修改吗?</p><div class="warning-box">❌ 不需要,您可以闭源分发。</div></div>
<div><p class="repo-font-semibold repo-mt-4">问:在云服务中使用需要付费吗?</p><div class="warning-box">❌ 不需要,但欢迎贡献代码。</div></div>
<div><p class="repo-font-semibold repo-mt-4">问:技术支持收费吗?</p><div class="success-box">✅ 永久免费技术支持,通过 GitCode Issues 或邮件联系。</div></div>
</div>
<!-- 7. 联系与支持 -->
<div class="content-card" id="contact">
<h2>7. 联系与支持</h2>
<div class="grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--repo-spacing-6);">
<div style="background: var(--repo-color-bg-secondary); padding: var(--repo-spacing-5); border-radius: var(--repo-radius-lg);">
<h4>📧 授权咨询</h4>
<p>me@dvsyun.top 或 dvs6666@163.com</p>
<p class="repo-text-sm repo-text-tertiary">7个工作日内回复</p>
</div>
<div style="background: var(--repo-color-bg-secondary); padding: var(--repo-spacing-5); border-radius: var(--repo-radius-lg);">
<h4>🛠️ 技术支持</h4>
<p>GitCode Issues · 官方文档 · 邮件支持</p>
<p class="repo-text-sm repo-text-success">✅ 永久免费</p>
</div>
</div>
<div class="repo-mt-6" style="background: var(--repo-color-bg-secondary); border-radius: var(--repo-radius-lg); padding: var(--repo-spacing-5);">
<h3>🔗 官方仓库</h3>
<div class="grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--repo-spacing-4); margin-top: var(--repo-spacing-3);">
<div>
<p>✅ <strong>主仓库:</strong><a href="https://gitcode.com/dvsxt/ap_ds" class="repo-text-link">GitCode</a></p>
<p>✅ <strong>国内镜像:</strong><a href="https://gitee.com/dssxt/ap_ds" class="repo-text-link">Gitee</a></p>
</div>
<div>
<p>❌ <strong>已废弃:</strong><a href="https://github.com/dvs-web/ap_ds" class="repo-text-danger">GitHub</a></p>
<p>🔗 <strong>项目主页:</strong><a href="https://apds.top" class="repo-text-link">apds.top</a></p>
</div>
</div>
</div>
</div>
<!-- 8. 最新公告 -->
<div class="content-card" id="announcement">
<h2>8. 最新公告</h2>
<div id="announcement-content" class="announcement-content">加载中...</div>
</div>
</div>
<script>
// 复制代码函数
window.copyCode = function(btn) {
const pre = btn.parentElement.querySelector('pre');
const code = pre.innerText;
navigator.clipboard.writeText(code).then(() => {
btn.classList.add('copied');
btn.innerHTML = '<i class="fa fa-check"></i> 已复制';
setTimeout(() => {
btn.classList.remove('copied');
btn.innerHTML = '<i class="fa fa-clipboard"></i> 复制';
}, 2000);
});
};
// 从后端获取版本信息
fetch('/api/version-info')
.then(res => res.json())
.then(data => {
if (data.title) document.getElementById('hero-title').innerHTML = data.title;
if (data.subtitle) document.getElementById('hero-subtitle').innerHTML = data.subtitle;
const badgesDiv = document.getElementById('version-badges');
if (data.version || data.size) {
badgesDiv.innerHTML = `
<span class="repo-tag repo-tag--primary">${data.version || 'v3.0.0 LTS'}</span>
<span class="repo-tag repo-tag--success">仅 ${data.size || '2.5MB'}</span>
<span class="repo-tag repo-tag--warning">跨平台 W/M/L</span>
`;
}
})
.catch(() => {});
// 加载最新公告
fetch('/api/latest-announcement')
.then(res => res.json())
.then(data => {
const container = document.getElementById('announcement-content');
if (data.html) {
container.innerHTML = data.html;
// 高亮代码块
if (typeof Prism !== 'undefined') {
Prism.highlightAllUnder(container);
}
// 为代码块添加复制按钮
container.querySelectorAll('pre').forEach(pre => {
if (!pre.parentElement.classList.contains('code-block')) {
const wrapper = document.createElement('div');
wrapper.className = 'code-block';
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
const btn = document.createElement('button');
btn.className = 'copy-btn';
btn.innerHTML = '<i class="fa fa-clipboard"></i> 复制';
btn.onclick = function() { window.copyCode(btn); };
wrapper.appendChild(btn);
}
});
} else {
container.innerHTML = '<p class="repo-text-tertiary">暂无公告</p>';
}
})
.catch(() => {
document.getElementById('announcement-content').innerHTML = '<p class="repo-text-tertiary">无法加载公告</p>';
});
</script>
{% endblock %}
+297
View File
@@ -0,0 +1,297 @@
<!-- 英文 SEO 标签 -->
<meta name="description" content="Official releases page for ap_ds. Full version list: v3.0.0 LTS (current stable), branch APDSLTS001, release date 2026-03-01. Each version includes ZIP/TAR.GZ/WHL downloads, detailed changelogs (deterministic cleanup, hash verification), upgrade guides, and version status labels (stable/withdrawn/incident).">
<meta name="keywords" content="ap_ds releases, download v3.0.0, changelog, LTS version, Python audio library stable release, APDSLTS001, hash verification, deterministic cleanup, WHL download">
<meta property="og:title" content="ap_ds Releases – v3.0.0 LTS & Version Archive">
<meta property="og:description" content="Official ap_ds release history. Download stable builds, read detailed changelogs, check version status (stable/withdrawn/incident).">
<meta property="og:type" content="software">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="ap_ds Releases & Changelogs">
<!-- 中文 SEO 标签 -->
<meta name="description" lang="zh-CN" content="ap_ds 官方发行版页面。完整版本列表:v3.0.0 LTS(当前稳定版),分支 APDSLTS001,发布日期 2026-03-01。每个版本附 ZIP/TAR.GZ/WHL 下载、详细更新日志(确定性资源清理、哈希验证)、升级指南及版本状态标注(稳定版/已撤回/开发事故)。">
<meta name="keywords" lang="zh-CN" content="ap_ds 发行版, 下载 v3.0.0, 更新日志, LTS版本, Python音频库稳定版, APDSLTS001, 哈希验证, 确定性清理, WHL下载">
<meta property="og:title" lang="zh-CN" content="ap_ds 发行版 – v3.0.0 LTS 及版本归档">
<meta property="og:description" lang="zh-CN" content="ap_ds 官方版本历史。下载稳定版,阅读详细更新日志,查看版本状态(稳定/已撤回/开发事故)。">{% extends "base.html" %}{% block content %}{% block title %}ap_ds Releases – Version History, Changelogs & Stable Downloads (v3.0.0 LTS){% endblock %}
<div class="repo-header">
<div class="repo-path">
<span>📦 发行版</span>
</div>
<p style="color: var(--repo-color-text-tertiary); margin-top: 8px;">所有 ap_ds 版本下载</p>
</div>
<!-- 版本列表 -->
<div class="file-list">
<div class="file-list-header">
<span>版本</span>
<span>分支</span>
<span>发布日期</span>
<span>下载</span>
</div>
{% for release in releases %}
<div class="file-row">
<div class="file-name" style="flex: 0.3;">
<span class="icon">🏷️</span>
<strong style="color: var(--repo-color-primary);">{{ release.version }}</strong>
</div>
<div class="file-message" style="flex: 0.3;">{{ release.branch }}</div>
<div class="file-time" style="flex: 0.2;">{{ release.date }}</div>
<div style="display: flex; gap: 8px; flex: 0.2;">
<a href="{{ url_for('download_zip', branch=release.branch, version=release.version) }}" class="btn btn-secondary" style="padding: 4px 8px;">ZIP</a>
<a href="{{ url_for('download_tar', branch=release.branch, version=release.version) }}" class="btn btn-secondary" style="padding: 4px 8px;">TAR.GZ</a>
<a href="{{ url_for('download_whl', version=release.version) }}" class="btn btn-primary" style="padding: 4px 8px;">WHL</a>
</div>
</div>
{% endfor %}
</div>
<!-- 版本详情 - 展开/收起 -->
<div class="releases-details">
{% for release in releases %}
<div class="release-card" data-version="{{ release.version }}">
<div class="release-header" onclick="toggleRelease('{{ release.version }}')">
<div class="release-header-left">
<span class="release-version">{{ release.version }}</span>
<span class="release-branch">{{ release.branch }}</span>
<span class="release-date">{{ release.date }}</span>
</div>
<div class="release-header-right">
<span class="release-status">
{% if release.has_whl %}
<span class="repo-tag repo-tag--success">PyPI</span>
{% endif %}
</span>
<span class="release-toggle" id="toggle-{{ release.version }}">▼</span>
</div>
</div>
<div class="release-body" id="release-{{ release.version }}" style="display: none;">
<div class="release-content">
{% if release.announcement %}
{{ release.announcement|safe }}
{% else %}
<div class="info-message">
📄 暂无版本说明文档。请查看 <a href="{{ url_for('view_file', file_path=release.branch + '/' + release.version + '/README.md', branch=release.branch, version=release.version) }}">README.md</a>
</div>
{% endif %}
</div>
<div class="release-downloads">
<a href="{{ url_for('download_zip', branch=release.branch, version=release.version) }}" class="btn btn-secondary">📦 下载 ZIP</a>
<a href="{{ url_for('download_tar', branch=release.branch, version=release.version) }}" class="btn btn-secondary">📦 下载 TAR.GZ</a>
<a href="{{ url_for('download_whl', version=release.version) }}" class="btn btn-primary">📦 下载 WHL</a>
<a href="{{ url_for('view_file', file_path=release.branch + '/' + release.version + '/README.md', branch=release.branch, version=release.version) }}" class="btn btn-secondary">📖 查看 README</a>
</div>
</div>
</div>
{% endfor %}
</div>
<style>
.releases-details {
margin-top: 32px;
display: flex;
flex-direction: column;
gap: 12px;
}
.release-card {
border: 1px solid var(--repo-color-border);
border-radius: var(--repo-radius-lg, 8px);
overflow: hidden;
background-color: var(--repo-color-bg);
}
.release-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background-color: var(--repo-color-bg-secondary);
cursor: pointer;
transition: background 0.2s;
}
.release-header:hover {
background-color: var(--repo-color-bg-hover);
}
.release-header-left {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
.release-version {
font-size: 18px;
font-weight: 600;
color: var(--repo-color-primary);
}
.release-branch {
font-size: 13px;
color: var(--repo-color-text-secondary);
background-color: var(--repo-color-bg-tertiary);
padding: 2px 8px;
border-radius: 20px;
}
.release-date {
font-size: 13px;
color: var(--repo-color-text-tertiary);
}
.release-header-right {
display: flex;
align-items: center;
gap: 16px;
}
.release-toggle {
font-size: 16px;
color: var(--repo-color-text-tertiary);
transition: transform 0.2s;
}
.release-card.open .release-toggle {
transform: rotate(180deg);
}
.release-body {
padding: 24px;
border-top: 1px solid var(--repo-color-border);
background-color: var(--repo-color-bg);
}
.release-content {
margin-bottom: 24px;
padding-bottom: 24px;
border-bottom: 1px solid var(--repo-color-border);
}
/* README 内容样式 */
.release-content h1, .release-content h2, .release-content h3 {
margin-top: 24px;
margin-bottom: 16px;
font-weight: 600;
}
.release-content h1 { font-size: 1.8em; border-bottom: 1px solid var(--repo-color-border); padding-bottom: 0.3em; }
.release-content h2 { font-size: 1.5em; border-bottom: 1px solid var(--repo-color-border); padding-bottom: 0.3em; }
.release-content h3 { font-size: 1.25em; }
.release-content h4 { font-size: 1.1em; }
.release-content p { margin-bottom: 16px; line-height: 1.6; }
.release-content code {
background-color: var(--repo-color-bg-tertiary);
padding: 0.2em 0.4em;
border-radius: 4px;
font-family: var(--repo-font-family-mono, monospace);
font-size: 0.9em;
}
.release-content pre {
background-color: var(--repo-color-bg-tertiary);
padding: 16px;
border-radius: 8px;
overflow-x: auto;
margin-bottom: 16px;
}
.release-content pre code {
background: none;
padding: 0;
}
.release-content ul, .release-content ol {
padding-left: 2em;
margin-bottom: 16px;
}
.release-content blockquote {
border-left: 4px solid var(--repo-color-border);
padding-left: 16px;
color: var(--repo-color-text-tertiary);
margin-bottom: 16px;
}
.release-content table {
border-collapse: collapse;
margin-bottom: 16px;
width: 100%;
}
.release-content th, .release-content td {
border: 1px solid var(--repo-color-border);
padding: 8px 12px;
}
.release-content th {
background-color: var(--repo-color-bg-secondary);
}
.release-content hr {
border: none;
border-top: 1px solid var(--repo-color-border);
margin: 24px 0;
}
.release-content img {
max-width: 100%;
}
.release-downloads {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.info-message {
padding: 16px;
background-color: var(--repo-color-bg-secondary);
border-radius: var(--repo-radius-md, 6px);
color: var(--repo-color-text-secondary);
text-align: center;
}
.repo-tag--success {
background-color: rgba(16, 185, 129, 0.1);
color: #10b981;
padding: 2px 8px;
border-radius: 20px;
font-size: 12px;
}
@media (max-width: 768px) {
.release-header-left {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.release-downloads {
flex-direction: column;
}
.release-downloads .btn {
text-align: center;
}
}
</style>
<script>
function toggleRelease(version) {
var body = document.getElementById('release-' + version);
var card = document.querySelector('.release-card[data-version="' + version + '"]');
if (body.style.display === 'none') {
body.style.display = 'block';
card.classList.add('open');
} else {
body.style.display = 'none';
card.classList.remove('open');
}
}
// 默认展开第一个版本
document.addEventListener('DOMContentLoaded', function() {
var firstRelease = document.querySelector('.release-card');
if (firstRelease) {
var version = firstRelease.getAttribute('data-version');
if (version) {
var body = document.getElementById('release-' + version);
if (body) {
body.style.display = 'block';
firstRelease.classList.add('open');
}
}
}
});
</script>
{% endblock %}
+59
View File
@@ -0,0 +1,59 @@
{% extends "base.html" %}
{% block title %}{{ metadata.name }} · ap_ds{% endblock %}
{% block content %}
<div class="repo-header">
<div class="repo-path">
<a href="/code">ap_ds</a>
<span class="separator">/</span>
<a href="{{ url_for('code_browser') }}?branch={{ branch }}">{{ branch }}</a>
<span class="separator">/</span>
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}">{{ version }}</a>
{% if branch and version %}
{# 将反斜杠替换为正斜杠 #}
{% set normalized_path = metadata.path.replace('\\', '/') %}
{% set relative_path = normalized_path.replace(branch + '/' + version + '/', '') %}
{% if relative_path and '/' in relative_path %}
{% set parts = relative_path.split('/') %}
{% for part in parts[:-1] %}
<span class="separator">/</span>
<a href="#">{{ part }}</a>
{% endfor %}
{% endif %}
{% endif %}
<span class="separator">/</span>
<span>{{ metadata.name }}</span>
</div>
</div>
<div class="code-header">
<div>
<span style="font-family: monospace;">{{ metadata.name }}</span>
<span style="margin-left: 12px; font-size: 12px; color: #848d97;">{{ metadata.size }} 字节</span>
</div>
<div style="display: flex; gap: 8px;">
<button class="copy-btn" onclick="copyCode(this)">复制代码</button>
<a href="{{ url_for('download_file', file_path=metadata.path.replace('\\', '/')) }}" class="copy-btn" style="text-decoration: none;">下载文件</a>
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}" class="copy-btn" style="text-decoration: none;">返回</a>
</div>
</div>
<div class="code-content">
<pre><code class="language-python" id="code-content">{{ content|escape }}</code></pre>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
hljs.highlightElement(document.querySelector('#code-content'));
});
function copyCode(btn) {
var code = document.getElementById('code-content').innerText;
navigator.clipboard.writeText(code).then(function() {
var originalText = btn.innerText;
btn.innerText = '已复制!';
setTimeout(function() { btn.innerText = originalText; }, 2000);
});
}
</script>
{% endblock %}
+40
View File
@@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block title %}{{ metadata.name }} - ap_ds{% endblock %}
{% block content %}
<div class="repo-container">
<div class="repo-card repo-mb-4">
<div class="repo-card__body">
<div class="repo-flex repo-justify-between repo-items-center">
<div>
<h2 class="repo-text-lg repo-font-semibold">{{ metadata.name }}</h2>
<div class="repo-text-sm repo-text-tertiary repo-mt-1">
{{ metadata.size }} 字节 · 最后修改 {{ metadata.modified }} · 类型: {{ metadata.extension }}
</div>
</div>
<div class="repo-flex repo-gap-2">
<a href="{{ url_for('download_file', file_path=metadata.path) }}" class="repo-btn repo-btn--sm repo-btn--primary">下载文件</a>
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}" class="repo-btn repo-btn--sm repo-btn--ghost">返回</a>
</div>
</div>
</div>
</div>
{% if is_text %}
<div class="repo-card">
<div class="repo-card__body">
<pre style="background: var(--repo-color-bg-tertiary); padding: 1rem; border-radius: 8px; overflow-x: auto; font-family: monospace; font-size: 13px;">{{ content }}</pre>
</div>
</div>
{% else %}
<div class="repo-card repo-text-center repo-py-8">
<div class="repo-card__body">
<span class="icon-file" style="font-size: 4rem; color: var(--repo-color-text-tertiary);"></span>
<p class="repo-text-secondary repo-mt-4">此文件类型无法在线预览</p>
<a href="{{ url_for('download_file', file_path=metadata.path) }}" class="repo-btn repo-btn--primary repo-mt-4">下载文件</a>
</div>
</div>
{% endif %}
</div>
{% endblock %}
+34
View File
@@ -0,0 +1,34 @@
{% extends "base.html" %}
{% block title %}{{ metadata.name }} · ap_ds{% endblock %}
{% block content %}
<div class="repo-header">
<div class="repo-path">
<a href="/code">ap_ds</a>
<span class="separator">/</span>
<a href="{{ url_for('code_browser') }}?branch={{ branch }}">{{ branch }}</a>
<span class="separator">/</span>
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}">{{ version }}</a>
<span class="separator">/</span>
<span>{{ metadata.name }}</span>
</div>
</div>
<div class="code-header">
<div>
<span style="font-family: monospace;">{{ metadata.name }}</span>
<span style="margin-left: 12px; font-size: 12px; color: var(--repo-color-text-tertiary);">{{ metadata.size }} 字节</span>
<span style="margin-left: 12px; font-size: 12px; color: var(--repo-color-text-tertiary);">最后修改 {{ metadata.modified }}</span>
</div>
<div style="display: flex; gap: 8px;">
<a href="{{ url_for('download_file', file_path=metadata.path) }}" class="copy-btn" style="text-decoration: none;">下载文件</a>
<a href="{{ url_for('code_browser') }}?branch={{ branch }}&version={{ version }}" class="copy-btn" style="text-decoration: none;">返回</a>
</div>
</div>
<!-- 关键修改:使用 |safe 过滤器 -->
<div class="readme-content" style="border: 1px solid var(--repo-color-border); border-top: none; border-radius: 0 0 var(--repo-radius-lg, 8px) var(--repo-radius-lg, 8px);">
{{ html_content|safe }}
</div>
{% endblock %}