├── LICENSE ├── add_idx.py ├── README.md └── ArcaeaCheck.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Wolken 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /add_idx.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | 4 | # 定义 songlist 文件路径 5 | songlist_path = './songs/songlist' 6 | 7 | # 读取 songlist 文件 8 | with open(songlist_path, "r", encoding="utf-8") as file: 9 | json_data = file.read() 10 | 11 | 12 | # 使用正则表达式匹配每个 "id" 之前的位置并插入索引 13 | def add_index(json_string): 14 | pattern = r'(\s*{)(?=\s*"id":)' # 匹配 `{` 后面的 `"id":`,不包含后者 15 | matches = list(re.finditer(pattern, json_string)) 16 | offset = 0 17 | 18 | for i, match in enumerate(matches, start=1): 19 | index_str = f'\n "idx": {i},' 20 | position = match.end() + offset # 插入到 `{` 的下一行 21 | json_string = json_string[:position] + index_str + json_string[position:] 22 | offset += len(index_str) 23 | 24 | return json_string 25 | 26 | 27 | # 更新后的 JSON 数据 28 | updated_json = add_index(json_data) 29 | 30 | # 创建原文件的备份 31 | backup_path = songlist_path + '.bak' 32 | os.rename(songlist_path, backup_path) 33 | 34 | # 将结果写入到原文件中 35 | with open(songlist_path, "w", encoding="utf-8") as output_file: 36 | output_file.write(updated_json) 37 | 38 | print(f"索引已成功添加,备份已创建为 {backup_path},并将更新后的数据写入到 {songlist_path} 文件中。") 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arcaea Files Structure Check 2 | # Arcaea文件结构检查 3 | `ArcaeaCheck` 4 | 5 | > [!IMPORTANT] 6 | > 使用前请先使用 `Visual Studio Code` 内的 [vscode-arcaea-file-format](https://github.com/yojohanshinwataikei/vscode-arcaea-file-format) 插件对 songlist 以及 packlist 文件内的**错误**和**警告**进行**修正**并且**格式化**。 7 | 8 | > [!NOTE] 9 | > packlist 中缺少属性 "section" 暂时不影响程序正常运行。 10 | 11 | # 文件结构定义 12 | 13 | ``` 14 | root/ 15 | ├── songs/ 16 | │ ├── pack/ 17 | │ │ ├── select_base.png 18 | │ │ └── ... 19 | │ ├── ignotusafterburn/ 20 | │ │ ├── 2.aff 21 | │ │ ├── base.jpg 22 | │ │ ├── base_256.jpg 23 | │ │ ├── base.ogg 24 | │ │ └── ... 25 | │ ├── songlist 26 | │ ├── packlist 27 | │ └── ... 28 | ├── img/ 29 | │ ├── bg/ 30 | │ │ ├── base_conflict.jpg 31 | │ │ ├── base_light.jpg 32 | │ │ └── ... 33 | │ └── ... 34 | ├── add_idx.py 35 | ├── ArcaeaCheck.py 36 | └── ... 37 | ``` 38 | 39 | # 功能 40 | 41 | ## `ArcaeaCheck.py`: 42 | 43 | - 检查 songlist 中 `"id"` 值是否存在重复。 44 | - 检查 songs 文件夹内是否缺少 songlist 中 `"id"` 值对应的文件夹。 45 | - 检查文件夹内是否存在 base.jpg、base.ogg、base_256.jpg 三个文件。 46 | - 检查 songlist 中 `"set"` 值是否存在于 packlist 的 `"id"` 值中。(忽略值为 `"single"` 的情况) 47 | - 检查 songlist 中的 `"bg"` 值是否在 bg 文件夹中存在对应的图片文件。 48 | - 检查 packlist 中的 `"id"` 值是否在 pack 文件夹中存在对应的图片文件。 49 | - 检查 songlist 内每首歌的 `"difficulties"` 下是否都存在 `"ratingClass"` 值为 `0`, `1`, `2` 的三个子代码块。 50 | - 若 `"difficulties"` 中存在 `"ratingClass": 3`,检查对应文件夹内是否存在 3.aff 文件。 51 | - 若不存在 `"ratingClass": 3` ,检查其对应文件夹内是否存在 2.aff 文件。 52 | - 检查 `"title_localized"` 下是否存在 `"en"` 项并且值为空。 53 | - 若存在 `"source_localized"`,检查值是否为空。 54 | - 检查 `"version"` 的值是否为空值。 55 | > 存在空值会导致在选择全曲时程序出错退出。 56 | - 检查 `"date"` 的值是否为十位数字时间戳。 57 | 58 | ## `add_idx.py` 59 | 60 | > [!TIP] 61 | > 若需删除`"idx"` 行,请善用[正则表达式](https://regexr-cn.com/)进行替换删除。 62 | 63 | - 按照 songlist 内的顺序在每首歌的代码块头部加入 `"idx"` 值。 64 | > 该值存在与否暂时仅影响 Link Play 功能,不会影响主程序。 65 | -------------------------------------------------------------------------------- /ArcaeaCheck.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import re 4 | 5 | # 定义文件路径 6 | songlist_path = './songs/songlist' # songlist 文件路径 7 | packlist_path = './songs/packlist' # packlist 文件路径 8 | songs_directory = './songs' # songs 文件夹路径 9 | img_bg_folder = './img/bg' # bg 文件夹路径 10 | pack_directory = './songs/pack' # pack 文件夹路径 11 | 12 | # 加载 songlist 文件 13 | with open(songlist_path, 'r', encoding='utf-8') as file: 14 | songlist_data = json.load(file) 15 | 16 | # 加载 packlist 文件 17 | with open(packlist_path, 'r', encoding='utf-8') as file: 18 | packlist_data = json.load(file) 19 | 20 | # 初始化数据容器 21 | song_ids = set() 22 | bg_filenames = [] 23 | 24 | # 检查背景文件夹存在性 25 | if not os.path.isdir(img_bg_folder): 26 | print(f"bg 文件夹 {img_bg_folder} 不存在,请检查路径。") 27 | 28 | if not os.path.isdir(pack_directory): 29 | print(f"pack 文件夹 {pack_directory} 不存在,请检查路径。") 30 | 31 | # 提取 songlist 中 id 和 bg 文件名 32 | for song in songlist_data['songs']: 33 | song_id = song['id'] 34 | bg_filename = song['bg'] 35 | 36 | # 检查 songlist 中的重复 id 37 | if song_id in song_ids: 38 | print(f"songlist \"id\":\"{song_id}\" 存在重复") 39 | else: 40 | song_ids.add(song_id) 41 | 42 | # 检查歌曲 id 是否有对应的文件夹 43 | song_folder_path = os.path.join(songs_directory, song_id) 44 | if not os.path.isdir(song_folder_path): 45 | print(f"songlist \"id\": \"{song_id}\" 未在 songs 文件夹内找到对应文件夹") 46 | else: 47 | # 检查歌曲文件夹内是否存在 base.jpg、base.ogg、base_256.jpg 文件 48 | required_files = ['base.jpg', 'base.ogg', 'base_256.jpg'] 49 | for file in required_files: 50 | file_path = os.path.join(song_folder_path, file) 51 | if not os.path.isfile(file_path): 52 | print(f"在 {song_id} 文件夹内,缺少文件:{file}") 53 | 54 | # 检查 ratingClass 3 并验证 3.aff 文件的存在 55 | has_rating_class_3 = False 56 | has_rating_class_0_1_2 = {0: False, 1: False, 2: False} # 用于检查 ratingClass 0, 1, 2 的存在 57 | rating_class_set = set() # 用于检查重复的 ratingClass 58 | 59 | for difficulty in song['difficulties']: 60 | rating_class = difficulty['ratingClass'] 61 | 62 | if rating_class in rating_class_set: 63 | print(f"歌曲 id {song_id} 的 ratingClass {rating_class} 重复") 64 | else: 65 | rating_class_set.add(rating_class) 66 | 67 | if rating_class == 3: 68 | has_rating_class_3 = True 69 | aff_file_path = os.path.join(song_folder_path, '3.aff') 70 | if not os.path.isfile(aff_file_path): 71 | print(f"songlist \"id\": \"{song_id}\" 存在 \"ratingClass\": 3,但在对应文件夹内缺少 3.aff 文件") 72 | 73 | # 检查 ratingClass 0, 1, 2 74 | if rating_class in has_rating_class_0_1_2: 75 | has_rating_class_0_1_2[rating_class] = True 76 | 77 | # 输出缺少 ratingClass 0, 1, 2 的信息 78 | missing_classes = [str(cls) for cls, exists in has_rating_class_0_1_2.items() if not exists] 79 | if missing_classes: 80 | print(f"songlist \"id\": \"{song_id}\" 缺少 \"ratingClass\": {', '.join(missing_classes)}") 81 | 82 | # 在 songlist 中不存在 ratingClass 3 时检查 2.aff 文件 83 | if not has_rating_class_3: 84 | aff_file_path = os.path.join(song_folder_path, '2.aff') 85 | if not os.path.isfile(aff_file_path): 86 | print(f"songlist \"id\": \"{song_id}\",无 \"ratingClass\": 3,并在对应文件夹内缺少 2.aff 文件") 87 | 88 | # 检查 title_localized 是否包含非空的 en 89 | if 'en' not in song['title_localized'] or not song['title_localized']['en']: 90 | print(f"songlist \"id\": \"{song_id}\",\"title_localized\" 缺少 \"en\" 或为空") 91 | 92 | # 检查 source_localized 是否存在并包含非空的 en 93 | if 'source_localized' in song: 94 | if 'en' not in song['source_localized'] or not song['source_localized']['en']: 95 | print(f"songlist \"id\": \"{song_id}\",\"source_localized\" 缺少 \"en\" 或为空") 96 | 97 | # 检查 version 是否为空 98 | if not song['version'].strip(): 99 | print(f"songlist \"id\": \"{song_id}\",\"version\" 值为空") 100 | 101 | # 检查 date 是否为十位数字时间戳 102 | if not re.match(r'^\d{10}$', str(song['date'])): 103 | print(f"songlist \"id\": \"{song_id}\",\"date\" 值不是十位数字时间戳") 104 | 105 | # 将 bg 文件名加入列表用于检查 106 | bg_filenames.append((bg_filename, song_id)) # 记录 bg 文件名和对应的 id 107 | 108 | # 提取 packlist 中的 pack id 以进行 set 验证 109 | pack_ids = {pack['id'] for pack in packlist_data['packs']} 110 | 111 | # 验证 songlist 中的 set 是否在 packlist 中存在,忽略 single 值 112 | for song in songlist_data['songs']: 113 | song_set = song['set'] 114 | song_id = song['id'] 115 | 116 | if song_set != 'single' and song_set not in pack_ids: 117 | print(f"packlist \"id\": \"{song_set}\" 不存在 (songlist id:\"{song_id}\")") 118 | 119 | # 检查 bg 文件的存在性 120 | for bg_filename, song_id in bg_filenames: 121 | bg_file_exists = any( 122 | os.path.isfile(os.path.join(img_bg_folder, f"{bg_filename}.{ext}")) 123 | for ext in ['jpg'] 124 | # for ext in ['jpg', 'jpeg', 'png', 'bmp', 'gif'] 125 | ) 126 | if not bg_file_exists: 127 | print(f"songlist \"id\": \"{song_id}\",\"bg\": \"{bg_filename}\",未在 bg 文件夹内找到对应的文件") 128 | 129 | # 检查 packlist 中 pack id 文件的存在性 130 | for pack in packlist_data['packs']: 131 | pack_id = pack['id'] 132 | pack_file_exists = any( 133 | os.path.isfile(os.path.join(pack_directory, f"select_{pack_id}.{ext}")) 134 | for ext in ['png'] 135 | # for ext in ['jpg', 'jpeg', 'png', 'bmp', 'gif'] 136 | ) 137 | if not pack_file_exists: 138 | print(f"packlist \"id\": \"{pack_id}\",未在 pack 文件夹中找到对应的图片文件") 139 | 140 | # 输出检查完成信息 141 | print("检查完成。") 142 | --------------------------------------------------------------------------------