docs: add project plan (PLAN.md) and .gitignore
This commit is contained in:
commit
a32169199f
2 files changed
+244
No files matched your search
+46
@@ -0,0 +1,46 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio cache/options
|
||||||
|
.vs/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# .NET
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# NuGet
|
||||||
|
*.nupkg
|
||||||
|
*.snupkg
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
|
||||||
|
# Rider
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
Thumbs.db
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
# SongTidy 项目计划
|
||||||
|
|
||||||
|
## 1. 项目概述
|
||||||
|
|
||||||
|
跨平台桌面应用,用于批量整理音频文件名:扫描/选择音频文件 → 解析文件名提取**歌手/歌曲名/注释** → 界面编辑(含一键交换歌手与歌曲名)→ 一键批量重命名。支持将编辑进度暂存为 XML「计划」文件,下次继续。
|
||||||
|
|
||||||
|
## 2. 技术栈
|
||||||
|
|
||||||
|
| 项 | 选型 |
|
||||||
|
|---|---|
|
||||||
|
| 框架 | .NET 10(`net10.0`,本地 SDK 10.0.401) |
|
||||||
|
| UI | Avalonia **11.3.22**(以本地 `avalonia.mvvm` 模板生成骨架后升级至此版本) |
|
||||||
|
| 主题/控件库 | **Semi.Avalonia 11.3.14**(替代模板自带主题,不使用默认界面库) |
|
||||||
|
| 图标库 | **IconPacks.Avalonia.Lucide 2.0.0** |
|
||||||
|
| MVVM | ReactiveUI + `Avalonia.ReactiveUI`(11.3.x,与 Avalonia 对齐) |
|
||||||
|
| 依赖注入 | 不引入 DI 容器,轻量组合(服务在 ViewModel 中直接构造) |
|
||||||
|
| 测试 | MSTest,独立项目 `SongTidy.Tests` |
|
||||||
|
|
||||||
|
以上库版本已验证可共存。`App.axaml` 中引入 Semi 主题替换模板默认主题;图标通过 IconPacks 的 Lucide 图标控件用于菜单项、交换按钮等处。
|
||||||
|
|
||||||
|
## 3. 领域规则
|
||||||
|
|
||||||
|
### 3.1 字段约束
|
||||||
|
|
||||||
|
- **歌曲名**:必选
|
||||||
|
- **歌手**:可选(对应不知道歌手的歌曲)
|
||||||
|
- **注释**:可选
|
||||||
|
|
||||||
|
### 3.2 文件名解析算法(纯字符串操作,不用正则)
|
||||||
|
|
||||||
|
输入为**不含扩展名**的文件名。
|
||||||
|
|
||||||
|
**步骤一 · 括号抽取(严格模式)**
|
||||||
|
|
||||||
|
1. 识别中文括号 `()` 与英文括号 `()` 两类,**同类才配对**(`(a)` 属落单)
|
||||||
|
2. 栈式扫描配对;**出现任何落单括号(未匹配的开或闭)→ 放弃整个抽取步骤**,全串原样进入步骤二(此时注释为空)
|
||||||
|
3. 每对括号:删去两侧括号符,内容 `Trim()`(仅标准空白,不含全角空格)作为注释素材
|
||||||
|
4. 抽到多个注释素材:以**单个半角空格**连接
|
||||||
|
5. 嵌套括号(罕见):最外层配对生效,内部括号作字面字符保留
|
||||||
|
6. 已抽取的括号对从源串中移除,剩余部分进入步骤二
|
||||||
|
|
||||||
|
**步骤二 · 连字符分割**
|
||||||
|
|
||||||
|
- 仅认半角 `-`(全角 `-`、破折号 `—`/`–` 等不认)
|
||||||
|
- 无 `-`:歌手为空,整串 `Trim()` 为歌曲名
|
||||||
|
- 有 `-`(多个时**以最后一个分割**):前段归歌手、后段归歌曲名(粗暴分配,交换按钮兜底),各自 `Trim()`
|
||||||
|
|
||||||
|
**示例**:`A - B (Live - Tokyo)(修正版).mp3` → 歌手 `A`,歌曲名 `B`,注释 `Live - Tokyo 修正版`
|
||||||
|
|
||||||
|
### 3.3 目标文件名格式(预设注册表,代码留扩展点)
|
||||||
|
|
||||||
|
| 歌手 | 注释 | 歌手在前 | 歌曲在前 |
|
||||||
|
|------|------|----------|----------|
|
||||||
|
| 有 | 有 | `A - N (C)` | `N (C) - A` |
|
||||||
|
| 有 | 无 | `A - N` | `N - A` |
|
||||||
|
| 无 | 有 | `N (C)` | `N (C)` |
|
||||||
|
| 无 | 无 | `N` | `N` |
|
||||||
|
|
||||||
|
- 注释括号固定半角 `()`
|
||||||
|
- **扩展名保留原样**,不参与解析与生成(flac 重命名后仍是 `.flac`)
|
||||||
|
- 实现为格式定义注册表(占位符 A/N/C + 各缺省分支模板),后续新增格式只需注册新定义
|
||||||
|
|
||||||
|
### 3.4 重命名校验关卡(全有或全无)
|
||||||
|
|
||||||
|
`输出 > 重命名` 点击后先做**全量校验**,任一问题即弹窗列出**全部**问题(含行号与原因)并拒绝执行:
|
||||||
|
|
||||||
|
1. 歌曲名为空
|
||||||
|
2. 字段含文件名非法字符(`\ / : * ? " < > |` 及控制字符),或目标名以点/空格结尾(Windows 限制)
|
||||||
|
3. 源文件已不存在(含 XML 加载后文件丢失的情形,在此统一拦截)
|
||||||
|
4. 列表中存在重复源路径(区分大小写的直接字符串比较,与合并规则一致)
|
||||||
|
5. 目标名冲突:多个条目生成相同目标名;或目标与磁盘既有文件冲突(自身源文件除外)——此项保持**大小写不敏感**,与文件系统实际行为一致(同目录不能同时存在 `a.mp3` 与 `A.mp3`),可挡住 `A→B` 且 `B→A` 这类顺序性冲突链
|
||||||
|
|
||||||
|
### 3.5 重命名执行
|
||||||
|
|
||||||
|
校验通过 → **MessageBox 式确认弹窗**(显示条目数与当前格式,防误触)→ 确认后批量执行 → 弹窗报告结果。执行成功后**条目保持不动**(仍指向旧文件名,再次重命名会被「文件缺失」拦截,属预期行为)。
|
||||||
|
|
||||||
|
## 4. 功能规格
|
||||||
|
|
||||||
|
### 4.1 主窗口
|
||||||
|
|
||||||
|
**菜单栏**:
|
||||||
|
|
||||||
|
- **文件**:新建计划|打开计划|—|保存计划|另存为计划|—|关闭计划|—|退出
|
||||||
|
- **输入**:从文件夹扫描|从文件选择
|
||||||
|
- **输出**:重命名
|
||||||
|
- **关于**:关于
|
||||||
|
|
||||||
|
**信息条**(DataGrid 上方):
|
||||||
|
|
||||||
|
- 左:`共 N 个条目`
|
||||||
|
- 右:命名格式选择器(歌手在前 / 歌曲在前),驱动目标文件名预览列实时刷新
|
||||||
|
|
||||||
|
**主区域 DataGrid**:
|
||||||
|
|
||||||
|
`#`|`原文件名`(只读)|`歌手`|`歌曲名`|`注释`(可编辑)|`目标文件名`(只读实时预览)|`⇄`(行内交换按钮,仅交换歌手与歌曲名,无批量操作、无重新解析)
|
||||||
|
|
||||||
|
无底部状态栏。
|
||||||
|
|
||||||
|
### 4.2 输入
|
||||||
|
|
||||||
|
- **从文件夹扫描**:系统文件夹选择器;**仅当前文件夹不递归**;按固定扩展名列表过滤:`mp3 flac wav ogg m4a aac wma opus`(代码常量)
|
||||||
|
- **从文件选择**:系统文件选择器,多选,过滤器允许任意文件(`所有文件 (*.*)`)
|
||||||
|
- **合并规则**:按完整路径判重(**区分大小写的直接字符串比较**,不做 Windows 大小写不敏感特判);出现重复条目时**新的解析结果直接丢弃,保留用户原条目(含已编辑内容)**;非重复条目追加到列表末尾
|
||||||
|
|
||||||
|
### 4.3 计划文档(XML 暂存)
|
||||||
|
|
||||||
|
**序列化实现**
|
||||||
|
|
||||||
|
- 使用 .NET 内置 `System.Xml.Serialization.XmlSerializer`
|
||||||
|
- 模型类**显式标注** `[XmlRoot]`、`[XmlElement]`、`[XmlAttribute]`、`[XmlArray]`/`[XmlArrayItem]`、`[XmlEnum]` 等 attribute 指定 node name,**不依赖类名/属性名推导**
|
||||||
|
|
||||||
|
**文件格式**(属性代替子节点,UTF-8,过滤器 `*.xml`):
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Plan version="1">
|
||||||
|
<Format>ArtistFirst</Format>
|
||||||
|
<Items>
|
||||||
|
<Item Path="D:\Music\A - B (Live).mp3" Artist="A" Title="B" Comment="Live"/>
|
||||||
|
<Item Path="D:\Music\C.mp3" Artist="" Title="C" Comment=""/>
|
||||||
|
</Items>
|
||||||
|
</Plan>
|
||||||
|
```
|
||||||
|
|
||||||
|
- `version`:文件版本号(`[XmlAttribute]`,当前 `1`,预留后续演进)
|
||||||
|
- `Format`:当前命名格式,以枚举名序列化(枚举成员用 `[XmlEnum]` 显式标注,如 `ArtistFirst`/`TitleFirst`)
|
||||||
|
- `Item`:`Path`/`Artist`/`Title`/`Comment` 全部为 XML 属性,空字段写空字符串(属性始终写出)
|
||||||
|
- 特殊字符(`&`、`"`、`<` 等)由 XmlSerializer 自动转义
|
||||||
|
|
||||||
|
**文档模型**
|
||||||
|
|
||||||
|
- 数据模型:全部条目(原始文件绝对路径 + 三字段当前值)+ 当前命名格式;无其他状态概念
|
||||||
|
- **加载计划时恢复主窗口格式选择器**;**切换格式也计入脏标记**
|
||||||
|
- **新建/关闭计划**:清空列表、清除路径
|
||||||
|
- **保存计划**:已有路径则原路径覆盖,否则走另存为;**另存为计划**:选择新路径保存
|
||||||
|
- **脏提示**:新建/打开/关闭计划及退出时,若有未保存修改弹窗三选:保存 / 不保存 / 取消
|
||||||
|
- 加载时**不校验**文件存在性(重命名关卡统一拦截)
|
||||||
|
|
||||||
|
### 4.4 弹窗基础设施
|
||||||
|
|
||||||
|
Avalonia 无内置 MessageBox,项目自建**通用 MessageBox 样式对话框组件**(标题 + 多行正文 + 可配置按钮组合,如 `确定`、`是/否`、`保存/不保存/取消`),全项目共用:
|
||||||
|
|
||||||
|
- 重命名前确认弹窗(防误触)
|
||||||
|
- 脏提示弹窗(保存 / 不保存 / 取消)
|
||||||
|
- 校验错误报告(正文为全部问题的多行清单)
|
||||||
|
- 重命名结果报告
|
||||||
|
|
||||||
|
(「关于」对话框为独立窗口,不套用 MessageBox)
|
||||||
|
|
||||||
|
## 5. 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
SongTidy/
|
||||||
|
├── SongTidy.csproj # net10.0;Avalonia 11.3.22、Semi.Avalonia 11.3.14、
|
||||||
|
│ # IconPacks.Avalonia.Lucide 2.0.0、Avalonia.ReactiveUI
|
||||||
|
├── Program.cs / App.axaml(.cs) / ViewLocator.cs # 模板骨架(App.axaml 换用 Semi 主题)
|
||||||
|
├── Models/
|
||||||
|
│ ├── SongPlan.cs # [XmlRoot("Plan")]:version 属性、Format、Items
|
||||||
|
│ ├── PlanItem.cs # [XmlArrayItem("Item")]:Path/Artist/Title/Comment 均为 [XmlAttribute]
|
||||||
|
│ └── RenameFormats.cs # 格式预设注册表(可扩展)+ 带 [XmlEnum] 的格式枚举
|
||||||
|
├── Services/
|
||||||
|
│ ├── FilenameParser.cs # 3.2 解析算法(纯静态)
|
||||||
|
│ ├── AudioFileScanner.cs # 文件夹扫描(非递归、扩展名过滤)
|
||||||
|
│ ├── RenameValidator.cs # 3.4 全量校验
|
||||||
|
│ ├── RenameExecutor.cs # 批量重命名
|
||||||
|
│ └── PlanXmlIO.cs # 计划 XML 读写(XmlSerializer)
|
||||||
|
├── ViewModels/
|
||||||
|
│ ├── ViewModelBase.cs
|
||||||
|
│ ├── MainWindowViewModel.cs # 菜单命令、计划文档状态(路径/脏标记)、格式选择、条目集合
|
||||||
|
│ └── SongEntryViewModel.cs # 行 VM:三字段、交换命令、目标名预览
|
||||||
|
├── Views/
|
||||||
|
│ ├── MainWindow.axaml(.cs)
|
||||||
|
│ └── Dialogs/
|
||||||
|
│ └── MessageBoxWindow.axaml(.cs) # 通用 MessageBox 组件 + 轻量调用辅助
|
||||||
|
SongTidy.Tests/ # MSTest:解析、格式生成、校验、XML IO
|
||||||
|
```
|
||||||
|
|
||||||
|
响应式要点:`ReactiveObject` + `ReactiveCommand`;预览列与信息条计数用 `WhenAnyValue`/`ObservableAsPropertyHelper` 派生;脏标记由字段编辑、集合变更、格式切换驱动;文件与文件夹选择器走 `StorageProvider`。
|
||||||
|
|
||||||
|
## 6. 实施步骤
|
||||||
|
|
||||||
|
1. `dotnet new avalonia.mvvm` 生成骨架,升级包版本至 Avalonia 11.3.22 / Semi.Avalonia 11.3.14 / IconPacks.Avalonia.Lucide 2.0.0 / Avalonia.ReactiveUI,`App.axaml` 接入 Semi 主题;建 `SongTidy.Tests`(MSTest)
|
||||||
|
2. 实现 `FilenameParser` + 解析单测(覆盖落单括号、多注释、多连字符、无连字符、全半角混合、嵌套括号等边界)
|
||||||
|
3. 实现 `RenameFormats` 注册表与生成逻辑 + 单测(四种组合 × 两种格式)
|
||||||
|
4. 实现 `PlanXmlIO` + 单测(格式枚举读写、空字段、路径含空格/特殊字符、`version` 保留)
|
||||||
|
5. 实现 `RenameValidator` + 单测(五类问题、冲突链)
|
||||||
|
6. 实现 ViewModel 层(文档模型、命令、脏跟踪、合并规则)
|
||||||
|
7. 实现主窗口视图(菜单栏、信息条、DataGrid 绑定、交换按钮、Lucide 图标)
|
||||||
|
8. 实现 `RenameExecutor`、MessageBox 通用组件与确认/报告/错误/脏提示弹窗
|
||||||
|
9. 「关于」对话框、窗口标题图标等收尾;全量测试通过
|
||||||
|
|
||||||
|
## 7. 非目标
|
||||||
|
|
||||||
|
- 不读写音频元数据标签(ID3 等)
|
||||||
|
- 不递归扫描子文件夹
|
||||||
|
- 无撤销功能(以执行前校验 + 确认弹窗兜底)
|
||||||
|
- 无批量交换、无重新解析
|
||||||
|
- 单窗口单计划,不支持多计划并行
|
||||||
Reference in new issue
Block a user