diff --git a/src/02.metadata-repository/00.readme.md b/src/02.metadata-repository/00.readme.md index b2a9f90..795e7de 100644 --- a/src/02.metadata-repository/00.readme.md +++ b/src/02.metadata-repository/00.readme.md @@ -2,7 +2,7 @@ > ⚠️️目前的元数据仓库标准仍处于 `Draft` 状态,尚未完全定稿。欢迎随时提出意见或建议。 -> ⚠️️目前的元数据仓库标准版本(`edition`)为 `1.0+alpha.1.5.1`。 +> ⚠️️目前的元数据仓库标准版本(`edition`)为 `1.0+alpha.2.0.0`。 尽管 `Anni` 约定对音频文件内嵌的标签有了严格的约束,但从实现的角度来看,如果所有的元数据都从音乐文件中获取,那么势必会导致如下问题: diff --git a/src/02.metadata-repository/01.folder-structure.md b/src/02.metadata-repository/01.folder-structure.md index cdf224a..c0873ee 100644 --- a/src/02.metadata-repository/01.folder-structure.md +++ b/src/02.metadata-repository/01.folder-structure.md @@ -4,13 +4,16 @@ ``` ├── album -│ ├── KSLA-0178.toml -│ └── @META-DUPLICATED -│ ├── @META-DUPLICATED.0.toml -│ └── @META-DUPLICATED.1.toml +│ └── dc +│ └── 0d +│ └── dc0d9856-3345-4a25-acb8-fccc7b959e3e.toml └── tag │ └── [Anime] 神様になった日.toml ├── repo.toml ``` -专辑存放的目录由 repo.toml 指定。 \ No newline at end of file +基本规则如下: + +- 专辑存放在 `album` 目录下,通过 `Album ID` 两级索引的方式管理。 +- 标签存放在 `tag` 目录下,文件名不限。 +- 仓库元数据存放在 `repo.toml` 文件中。 diff --git a/src/02.metadata-repository/05.repo-metadata.md b/src/02.metadata-repository/05.repo-metadata.md index 7b45047..b70b347 100644 --- a/src/02.metadata-repository/05.repo-metadata.md +++ b/src/02.metadata-repository/05.repo-metadata.md @@ -9,11 +9,5 @@ # 仓库名 name = "Yesterday17's Metadata Repo" # 仓库使用的元数据仓库描述版本 -edition = "1.0" -# 存放专辑的子目录,默认为 ['album'] -albums = ['album-beta', 'album'] +edition = "1.0+alpha.2.0.0" ``` - -## 专辑子目录 - -专辑可以存放在任意指定的子目录中,方便整理者以目录为单位对专辑信息进行分类。 diff --git a/src/06.anniv/03.playlist.md b/src/06.anniv/03.playlist.md index 7dcfa17..7bc231f 100644 --- a/src/06.anniv/03.playlist.md +++ b/src/06.anniv/03.playlist.md @@ -127,7 +127,7 @@ interface BasePlaylistItem { } // 普通音轨 -interface PlaylistItemTrack extends BasePlaylistItem { +interface PlaylistItemTrack extends BasePlaylistItem { type: "normal"; } diff --git a/src/06.anniv/08.meta.md b/src/06.anniv/08.meta.md index 5511453..a785bb6 100644 --- a/src/06.anniv/08.meta.md +++ b/src/06.anniv/08.meta.md @@ -2,14 +2,6 @@ `Anniv` 提供对元数据仓库中元数据进行索引的能力。 -## 结构定义 - -```ts -type AlbumTitle = { album_title: string }; - -type TrackInfoWithAlbum = TrackIdentifier & Required & AlbumTitle; -``` - ## 获取 Tag 列表 `GET /api/meta/tags` diff --git a/src/06.anniv/09.search.md b/src/06.anniv/09.search.md index 32da524..576a5bc 100644 --- a/src/06.anniv/09.search.md +++ b/src/06.anniv/09.search.md @@ -22,7 +22,7 @@ ```ts interface SerachResult { albums?: AlbumDetail[]; - tracks?: TrackInfoWithAlbum[]; + tracks?: TrackIdentifier[]; playlists?: PlaylistInfo[]; } ``` diff --git a/src/06.anniv/10.favorite.md b/src/06.anniv/10.favorite.md index 3ec9bee..9868cca 100644 --- a/src/06.anniv/10.favorite.md +++ b/src/06.anniv/10.favorite.md @@ -10,7 +10,7 @@ #### 返回参数 -返回 `TrackInfoWithAlbum[]` ,按照添加时间倒序排序。 +返回 `TrackIdentifier[]` ,按照添加时间倒序排序。 ### 添加单曲 diff --git a/src/08.annim/00.readme.md b/src/08.annim/00.readme.md new file mode 100644 index 0000000..dcd5300 --- /dev/null +++ b/src/08.annim/00.readme.md @@ -0,0 +1,32 @@ +# Annim + +> ⚠️️ 目前的 `Annim` 仍处于 `Draft` 状态,尚未完全定稿。欢迎随时提出意见或建议。 + +## 背景 + +在 `Annim` 提出之前,我们已经拥有了用于储存元数据的[元数据仓库(`repo`)](../02.metadata-repository/00.readme.md),以及用于针对性获取元数据的 [`Anniv` 元数据接口](../06.anniv/08.meta.md)。 + +但在实践中,我们发现: + +1. 元数据仓库本身的维护成本较高,以 `Git` 仓库的形式组织虽然降低了协作成本,提供了版本控制的能力,但本质上仍然是一套兼容性的方案,具有一定的学习成本。 +2. 将 `Anniv` 和元数据绑定的行为背离了元数据仓库建立的初衷。我们希望元数据是去中心化的,而 `Anniv` 却只能基于单一的元数据仓库进行元数据的分发。这导致了目前 `Project Anni` 中事实性地只存在一套元数据仓库,且新仓库的引入存在一定的难度。 +3. 「音频仓库」和「元数据」之间的关联被耦合成了「音频仓库」与「Anniv」之间的耦合。但由于设计上的非耦合性,导致 `Anniv` 无法获取一些直接的上下文。以搜索为例,`Anniv` 无法得知用户使用的 `Annil` 中拥有哪些专辑,因此也无法提供合理意义上的搜索结果。 +4. 目前单一元数据仓库的设计导致元数据的更新成本较高,无法较快地响应新内容的加入。 + +由此,我们提出 `Annim` 的概念,作为上述问题的一种解决方案。 + +## 设计理念 + +`Annim` 围绕这以下几个核心理念展开: + +1. 「元数据」与「音频仓库」绑定,与 `Anniv` 解耦。 + +`Annim` 的第一个任务就是将元数据相关的能力从 `Anniv` 中剥离。我们计划在 `Annim` 中提供一套 `GraphQL` 接口供客户端使用,以增加客户端获取元数据的灵活度。 + +在这一套纯查询接口的之上,未来我们还可以基于这一套接口定义,增加与元数据编辑、修改相关的能力,最终实现元数据的动态管理能力。 + +2. 多级元数据。 + +`Project Anni` 的核心目标之一是提供准确、完整的元数据。但无法忽略的是,整理获得完善的元数据需要大量的人力与时间成本,而听歌这件事情本身也是 `Project Anni` 的核心目标之一。因此,我们希望在获得准确无误的元数据之前,也能够存在一套「不完全正确但可用」的元数据,以达到最基本的使用目的。 + +为此,我们在 `Annim` 中提出了多级元数据的概念。它允许多个声明兼容的元数据源通过一定的形式共存,对客户端以单一元数据源的形式呈现。 diff --git a/src/08.annim/01.definition.md b/src/08.annim/01.definition.md new file mode 100644 index 0000000..4bf0433 --- /dev/null +++ b/src/08.annim/01.definition.md @@ -0,0 +1,5 @@ +# 术语与定义 + +- Anni 元数据协议:`Project Anni` 中客户端用于获取元数据的协议。 +- 元数据源:符合 Anni 元数据协议,即上述协议的元数据提供方。 +- 元数据仓库:元数据源的文本化存储形式,通常以 `Git` 仓库的形式存在。 diff --git a/src/08.annim/02.protocol.md b/src/08.annim/02.protocol.md new file mode 100644 index 0000000..d67b122 --- /dev/null +++ b/src/08.annim/02.protocol.md @@ -0,0 +1,288 @@ +# Anni 元数据协议 + +## 版本信息 + +> ⚠️️ 在协议版本迈入 `1.0` 之前,任何改动都有可能发生。 + +最后修改:2024 年 08 月 16 日 +协议版本:0.0.1-SNAPSHOT + +## 定义 + +`Anni` 元数据源(`Anni Metadata Source`,以下简称元数据源)是指实现了 `Anni` 元数据协议中定义接口的服务端应用程序。其核心功能有三: + +1. 对专辑元数据的查询 +2. 对标签及相关图的查询 +3. 给定关键词的专辑元数据搜索 + +## 底层协议 + +Anni 元数据协议基于 `GraphQL` 协议。客户端通过 `GraphQL` 查询语言向元数据源发起请求,元数据源返回符合 `GraphQL` 规范的响应。 + +## 结构定义 + +### 基本结构定义 + +```graphql +###################################################### +# 基本专辑信息 +type BaseAlbumInfo { + # + # 专辑名称 [必填项][ALBUM] + # + # 专辑的标题,盘片类型信息需要填写到专辑类型下。 + title: String! + # + # 专辑类型 [可选项][ALBUM] + # + # 当该项非空时,写入 ALBUM 中的专辑名为 "{title}【{edition}】"。 + edition: String + # + # 专辑品番 [可选项] + # + # 当存在多张时用 ~ 隔开,如: + # catalog = "ALBUM-01~50" + catalog: String + # + # 专辑艺术家 [必填项] + # + # 使用「Anni 艺术家别名嵌套」格式表示具体的艺术家。 + artist: String! + # + # 专辑的发行日期 [必填项][DATE] + # + # 日期有两种表示方法: + # date = 2021-06-22 + # date = "2021-06" + # + # 日期表达形式更加精确,当没有精确日期时则可以使用字符串表达模糊的年月。 + # 在字符串表达格式通过 `-` 分隔年月日,月和日都可以省略。 + # 尽量精确到日,如果无法确定则精确到月或年。 + releaseDate: String! +} + +###################################################### +# 基本光盘信息 +type BaseDiscInfo { + # 光盘名称 [可选项] + title: String + # 光盘艺术家 [可选项] + artist: String + # + # 光盘品番 [必填项] + # + # 只允许表示一张光盘,不得出现 ~ 连接符。 + catalog: String +} + +###################################################### +# 基本音轨信息 +type BaseTrackInfo { + # 音轨标题 [必填项][TITLE] + title: String! + # 艺术家 [必填项][ARTIST] + artist: String! + # 音乐类型 [必填项] + type: TrackType! +} + +# 音乐类型定义 +enum TrackType { + # normal(默认):有人声的歌曲。 + NORMAL + # instrumental:无人声的伴奏。 + INSTRUMENTAL + # absolute:纯音乐。 + ABSOLUTE + # drama:以人声为主的单元剧。 + DRAMA + # radio:以人声为主的广播节目。 + RADIO + # vocal:纯人声,如音乐现场的 MC 等。 + VOCAL + # others:其他类型,不建议使用。出现时建议向 Project Anni 官方仓库提交识别请求。 + OTHERS +} + +###################################################### +# 基本标签信息 +type BaseTag { + # + # Tag 名称 [必填项] + # + # 标签的名称,前后不带空格 + name: String! + # + # Tag 类型 [必填项] + # + # 用于区别不同类型的 Tag。所有相同类型的 Tag 实际拥有以该类型名命名的隐式 Parent Tag。 + type: TagType! +} + +# 标签类型 +enum TagType { + # artist: 单艺术家 + ARTIST + # group: 多艺术家组合 + GROUP + # animation: 动画 + ANIMATION + # radio: 广播节目 + RADIO + # series: 系列 + SERIES + # project: 企划 + PROJECT + # game: 游戏 + GAME + # organization: 组织、社团、公司 + ORGANIZATION + # others: 【不建议】默认,未分类 + OTHERS +} +``` + +### 扩展结构定义 + +```graphql +type ExtendedMetadata { + artists: JSONObject + tags: [TagInfo!]! +} + +type ExtendedTagInfo { + names: JSONObject +} +``` + +### 专辑定义 + +```graphql +type AlbumInfo { + ...BaseAlbumInfo + ...ExtendedMetadata + + albumId: String! + level: MetadataSortLevel! + + discs: [DiscInfo!]! +} + +type DiscInfo { + ...BaseDiscInfo + ...ExtendedMetadata + + tracks: [TrackInfo!]! +} + +type TrackInfo { + ...BaseTrackInfo + ...ExtendedMetadata +} + +type TagInfo { + ...BaseTag + ...ExtendedTagInfo + + includes: [TagInfo!]! + includedBy: [TagInfo!]! +} + +enum MetadataSortLevel { + # Level 4:已完成。元数据已整理完成,不会发生变更。 + # + # 整理者行为:无法改变。 + # 客户端行为:可以永久缓存该元数据,无需考虑变更。 + FINISHED + # Level 3:已审阅。元数据已经由部分整理者审阅并确认,较为可靠。 + # + # 整理者行为:可以改变,但需要注意客户端可能需要较长时间刷新。 + # 客户端行为:可以较长时间缓存该元数据。 + REVIEWED + # Level 2:部分完成。原则性信息(如碟片数量、曲目数量)确认正确且不会变更。 + # + # 整理者行为:可以在这个等级上停留较长时间,但需要尽快完善该元数据,并且通过审阅者确认后提升到 CONFIRMED 等级。 + # 客户端行为:可以缓存该元数据,但每 1h 需要检查是否存在变更。 + PARTIAL + # Level 1:开始整理。可能出现原则性错误,如专辑曲目数量不匹配。 + # + # 整理者行为:需要尽快完善该元数据,升级到 PARTIAL 等级。 + # 客户端行为:仅在纯离线状态下可使用缓存,其他场景下**必须**实时查询。 + INITIAL +} +``` + +## 查询接口 + +### 服务器信息查询 + +```graphql +type ServerInfo { + # 服务器名称 + name: String! + # 服务器版本 + version: String! + + # 服务器唯一标识 + annimRepositoryId: String! + # 服务器协议版本 + annimProtocolVersion: String! + + # 服务器最后更新时间 + lastUpdate: String! +} + +type MetadataQuery { + # 查询服务器信息 + info(): ServerInfo! +} +``` + +### 专辑元数据查询 + +```graphql +type MetadataQuery { + # 通过 Album ID 查询对应的专辑元数据 + album(albumId: String!): AlbumInfo + # 批量查询专辑元数据 + albums(albums: [String!]!): [AlbumInfo]! +} +``` + +### 标签查询 + +```graphql +type MetadataQuery { + # 通过标签名查询标签 + tag(name: String!): TagInfo + # 查询根标签 + rootTags(type: String): [TagInfo!]! + + # 通过标签名查询专辑 + albumsByTag(tag: String!): [AlbumInfo!]! + # 通过标签名查询曲目 + tracksByTag(tag: String!): [TrackInfo!]! +} +``` + +### 搜索 + +```graphql +type MetadataQuery { + # 通过关键词搜索专辑 + searchAlbum(keyword: String!): [AlbumInfo!]! + # 通过关键词搜索曲目 + searchTrack(keyword: String!): [TrackInfo!]! + # 通过关键词搜索标签 + searchTag(keyword: String!): [TagInfo!]! +} +``` + +## 元数据编辑 + +```graphql +type Mutations { + # 添加专辑元数据 + addAlbum(album: AlbumInfo!): AlbumInfo +} +``` diff --git a/src/50.anni-cli/05.workspace.md b/src/50.anni-cli/05.workspace.md index 7f81e25..d3d9755 100644 --- a/src/50.anni-cli/05.workspace.md +++ b/src/50.anni-cli/05.workspace.md @@ -60,13 +60,14 @@ │ ├── tag │ ├── repo.toml │ └── Readme.md +├── annim.db └── config.toml ``` -该目录中保存了音频整理所需的两大元素:音频资源和元数据仓库。目前根目录下定义的内容如下: +该目录中保存了音频整理所需的两大元素:音频资源和元数据。目前根目录下定义的内容如下: 1. 音频资源,以**严格目录格式**存放于 `objects` 目录下,层级为 2。当专辑目录下存在 `.publish` 文件时,该专辑的状态为**已发布**。 -2. 元数据仓库,位于 `repo` 目录。 +2. 元数据。根据配置文件的定义,可能是位于 `repo` 目录下的元数据仓库,也有可能是 `Annim` 服务器。 3. `config.toml`,负责记录一些额外的配置信息。 ### 用户目录 @@ -142,6 +143,19 @@ [workspace] publish-to = ["default"] +[workspace.metadata] +# repo [default] | remote +# +# repo +# - 从 WORKSPACE_ROOT/repo 中获取元数据 +# remote +# - 从远程服务器获取元数据 +type = "repo" +# [Optional] Annim 服务器的地址 +endpoint = "https://example.com/" +# [Optional] Annim 服务器的鉴权凭据 +token = "114514" + [library.default] path = "/home/yesterday17/Music" layers = 2 @@ -155,9 +169,8 @@ layers = 2 - [ ] 全新创建工作空间 - [x] `--repo`:从指定位置 `clone` 元数据仓库 -- [x] `--repo-config`:使用元数据仓库的 `repo.toml` 作为工作空间的配置文件 - \ - 创建从 `.anni/config.toml` 到 `.anni/repo/repo.toml` 的符号链接。 +- [x] `--remote`:从远程 `Annim` 初始化工作空间 +- [x] `--auth`:指定远程 `Annim` 的鉴权凭据 ### status @@ -178,9 +191,9 @@ layers = 2 将专辑的状态变更为已跟踪。状态变更前,`anni` 会输出一些信息供对比。 -- [x] `-t`, `--tags`:变更状态的同时,向元数据仓库导入音频中内嵌的元数据 - [x] `-y`, `--yes`:跳过信息检查 - [x] `d`, `--dry-run`:不实际变更专辑状态,仅运行流程。该属性搭配 `-t` 使用可以仅导入专辑元数据 +- [x] `-t`, `--tags`:变更状态的同时,向元数据仓库或 `Annim` 导入音频中内嵌的元数据 - [x] `e`, `--editor`:状态变更后,打开元数据仓库中的元数据文件 ### rm @@ -214,6 +227,8 @@ layers = 2 ### serve +_[Deprecated]_ + 启动工作空间的服务端环境,包括: - [ ] 一个基于严格目录格式的 `Annil` 服务端 diff --git a/src/SUMMARY.md b/src/SUMMARY.md index d1a1ed0..c446b15 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -66,6 +66,9 @@ - [实现的 API](./07.annisonic/01.apis.md) - [已测试的客户端](./07.annisonic/02.clients.md) - [部署向导](./07.annisonic/03.deploy.md) +- [Annim](./08.annim/00.readme.md) + - [术语与定义](./08.annim/01.definition.md) + - [Anni 元数据协议](./08.annim/02.protocol.md) ---