nnnotes 0.1.1__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. {nnnotes-0.1.1 → nnnotes-0.1.3}/PKG-INFO +30 -25
  2. nnnotes-0.1.1/src/nnnotes.egg-info/PKG-INFO → nnnotes-0.1.3/README.en.md +10 -36
  3. {nnnotes-0.1.1 → nnnotes-0.1.3}/pyproject.toml +10 -10
  4. nnnotes-0.1.3/rust/Cargo.lock +215 -0
  5. nnnotes-0.1.3/rust/Cargo.toml +23 -0
  6. nnnotes-0.1.3/rust/build.rs +33 -0
  7. nnnotes-0.1.3/rust/rustfmt.toml +2 -0
  8. nnnotes-0.1.3/rust/src/lib.rs +116 -0
  9. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/__init__.py +1 -1
  10. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/addressables.py +56 -10
  11. nnnotes-0.1.3/src/nnnotes/apkset.py +103 -0
  12. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/catalog.py +70 -20
  13. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/catalogdb.py +17 -10
  14. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/cli.py +111 -23
  15. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/cli_assets.py +67 -36
  16. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/config.py +42 -0
  17. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/configfile.py +12 -3
  18. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/cri.py +2 -1
  19. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/crikey.py +4 -3
  20. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/crilips.py +2 -1
  21. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/cristages.py +8 -3
  22. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/deckdata.py +74 -74
  23. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/gameapi.py +19 -7
  24. nnnotes-0.1.3/src/nnnotes/jp.py +294 -0
  25. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/liveaudio.py +2 -2
  26. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/master.py +31 -5
  27. nnnotes-0.1.3/src/nnnotes/musicdata.py +1144 -0
  28. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/nnnotes.example.toml +15 -2
  29. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/player.py +7 -2
  30. nnnotes-0.1.3/src/nnnotes/replaydata.py +101 -0
  31. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/room.py +10 -4
  32. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/storysite.py +2 -0
  33. nnnotes-0.1.3/src/nnnotes/ui.py +310 -0
  34. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/unity.py +12 -7
  35. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/web.py +23 -3
  36. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/webmodel.py +45 -17
  37. nnnotes-0.1.1/README.en.md +0 -191
  38. nnnotes-0.1.1/README.md +0 -144
  39. nnnotes-0.1.1/setup.cfg +0 -4
  40. nnnotes-0.1.1/src/nnnotes.egg-info/SOURCES.txt +0 -143
  41. nnnotes-0.1.1/src/nnnotes.egg-info/dependency_links.txt +0 -1
  42. nnnotes-0.1.1/src/nnnotes.egg-info/entry_points.txt +0 -2
  43. nnnotes-0.1.1/src/nnnotes.egg-info/requires.txt +0 -16
  44. nnnotes-0.1.1/src/nnnotes.egg-info/top_level.txt +0 -1
  45. nnnotes-0.1.1/tests/test_advvideo.py +0 -522
  46. nnnotes-0.1.1/tests/test_atom_eval.py +0 -231
  47. nnnotes-0.1.1/tests/test_atoms_mesh.py +0 -357
  48. nnnotes-0.1.1/tests/test_atoms_sprite.py +0 -235
  49. nnnotes-0.1.1/tests/test_atoms_texture.py +0 -272
  50. nnnotes-0.1.1/tests/test_blob_memo.py +0 -101
  51. nnnotes-0.1.1/tests/test_cache.py +0 -107
  52. nnnotes-0.1.1/tests/test_catalog.py +0 -206
  53. nnnotes-0.1.1/tests/test_catalog_lazy.py +0 -194
  54. nnnotes-0.1.1/tests/test_catalogdb.py +0 -320
  55. nnnotes-0.1.1/tests/test_census.py +0 -211
  56. nnnotes-0.1.1/tests/test_cli.py +0 -373
  57. nnnotes-0.1.1/tests/test_cli_assets.py +0 -1007
  58. nnnotes-0.1.1/tests/test_compress.py +0 -67
  59. nnnotes-0.1.1/tests/test_config.py +0 -193
  60. nnnotes-0.1.1/tests/test_configfile.py +0 -203
  61. nnnotes-0.1.1/tests/test_contract.py +0 -240
  62. nnnotes-0.1.1/tests/test_crilips.py +0 -199
  63. nnnotes-0.1.1/tests/test_cristages.py +0 -554
  64. nnnotes-0.1.1/tests/test_deckdata.py +0 -461
  65. nnnotes-0.1.1/tests/test_gameapi.py +0 -393
  66. nnnotes-0.1.1/tests/test_goldens.py +0 -125
  67. nnnotes-0.1.1/tests/test_jsonio.py +0 -45
  68. nnnotes-0.1.1/tests/test_layout.py +0 -363
  69. nnnotes-0.1.1/tests/test_link.py +0 -279
  70. nnnotes-0.1.1/tests/test_live_options.py +0 -298
  71. nnnotes-0.1.1/tests/test_live_options_web.py +0 -166
  72. nnnotes-0.1.1/tests/test_master.py +0 -132
  73. nnnotes-0.1.1/tests/test_mesh_texture_guards.py +0 -94
  74. nnnotes-0.1.1/tests/test_modelstages.py +0 -422
  75. nnnotes-0.1.1/tests/test_objexport.py +0 -1016
  76. nnnotes-0.1.1/tests/test_orchestrate.py +0 -305
  77. nnnotes-0.1.1/tests/test_pipeline.py +0 -163
  78. nnnotes-0.1.1/tests/test_plan.py +0 -149
  79. nnnotes-0.1.1/tests/test_player.py +0 -140
  80. nnnotes-0.1.1/tests/test_readsets.py +0 -442
  81. nnnotes-0.1.1/tests/test_regions.py +0 -246
  82. nnnotes-0.1.1/tests/test_repo.py +0 -50
  83. nnnotes-0.1.1/tests/test_room.py +0 -359
  84. nnnotes-0.1.1/tests/test_sniff.py +0 -137
  85. nnnotes-0.1.1/tests/test_speed_exact.py +0 -521
  86. nnnotes-0.1.1/tests/test_spot.py +0 -159
  87. nnnotes-0.1.1/tests/test_stages.py +0 -274
  88. nnnotes-0.1.1/tests/test_store.py +0 -181
  89. nnnotes-0.1.1/tests/test_story_closure.py +0 -137
  90. nnnotes-0.1.1/tests/test_story_fonts.py +0 -842
  91. nnnotes-0.1.1/tests/test_story_host.py +0 -512
  92. nnnotes-0.1.1/tests/test_story_kinds.py +0 -100
  93. nnnotes-0.1.1/tests/test_story_site.py +0 -663
  94. nnnotes-0.1.1/tests/test_story_ui.py +0 -551
  95. nnnotes-0.1.1/tests/test_textstyle.py +0 -253
  96. nnnotes-0.1.1/tests/test_typetrees.py +0 -382
  97. nnnotes-0.1.1/tests/test_views.py +0 -749
  98. nnnotes-0.1.1/tests/test_voices.py +0 -756
  99. nnnotes-0.1.1/tests/test_voices_story.py +0 -541
  100. nnnotes-0.1.1/tests/test_web.py +0 -277
  101. nnnotes-0.1.1/tests/test_webmodel.py +0 -675
  102. {nnnotes-0.1.1 → nnnotes-0.1.3}/LICENSE +0 -0
  103. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/__main__.py +0 -0
  104. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/acb.py +0 -0
  105. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/adv.py +0 -0
  106. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/advcommand.py +0 -0
  107. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/advmedia.py +0 -0
  108. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/advscene.py +0 -0
  109. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/advui.py +0 -0
  110. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/advvideo.py +0 -0
  111. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/atoms/__init__.py +0 -0
  112. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/atoms/astc.py +0 -0
  113. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/atoms/gltf.py +0 -0
  114. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/atoms/mesh.py +0 -0
  115. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/atoms/png.py +0 -0
  116. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/atoms/reader.py +0 -0
  117. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/atoms/sniff.py +0 -0
  118. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/atoms/sprite.py +0 -0
  119. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/atoms/texture.py +0 -0
  120. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/cache.py +0 -0
  121. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/census.py +0 -0
  122. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/compress.py +0 -0
  123. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/contract.py +0 -0
  124. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/export.py +0 -0
  125. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/jsonio.py +0 -0
  126. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/languages.py +0 -0
  127. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/layout.py +0 -0
  128. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/link.py +0 -0
  129. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/live.py +0 -0
  130. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/live2d.py +0 -0
  131. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/livenotes.py +0 -0
  132. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/liveoptions.py +0 -0
  133. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/livescene.py +0 -0
  134. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/liveui.py +0 -0
  135. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/modelstages.py +0 -0
  136. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/motion.py +0 -0
  137. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/objexport.py +0 -0
  138. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/orchestrate.py +0 -0
  139. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/plan.py +0 -0
  140. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/score.py +0 -0
  141. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/shader.py +0 -0
  142. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/spot.py +0 -0
  143. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/stages.py +0 -0
  144. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/store.py +0 -0
  145. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/story.py +0 -0
  146. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/storyfonts.py +0 -0
  147. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/storyhost.py +0 -0
  148. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/storyvoices.py +0 -0
  149. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/textstyle.py +0 -0
  150. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/tmpfont.py +0 -0
  151. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/typetrees/6000.3.12f1.json +0 -0
  152. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/viewrules.json +0 -0
  153. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/views.py +0 -0
  154. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/voices.py +0 -0
  155. {nnnotes-0.1.1 → nnnotes-0.1.3}/src/nnnotes/webaudio.py +0 -0
@@ -1,36 +1,39 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nnnotes
3
- Version: 0.1.1
4
- Summary: Offline data toolkit for BanG Dream! Our Notes game files: catalogs, bundles, master data, stories, Live2D, audio and live charts
5
- Author: MetaMiku, emptysekai
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/MetaSekaiLab/nnnotes
8
- Project-URL: Source, https://github.com/MetaSekaiLab/nnnotes
9
- Project-URL: Issues, https://github.com/MetaSekaiLab/nnnotes/issues
3
+ Version: 0.1.3
10
4
  Classifier: Programming Language :: Python :: 3
11
5
  Classifier: Operating System :: OS Independent
12
6
  Classifier: Topic :: Multimedia
13
- Requires-Python: >=3.11
14
- Description-Content-Type: text/markdown
15
- License-File: LICENSE
16
- Requires-Dist: UnityPy>=1.25
7
+ Requires-Dist: unitypy>=1.25
17
8
  Requires-Dist: numpy>=2.0
18
- Requires-Dist: Pillow>=10.0
9
+ Requires-Dist: pillow>=10.0
19
10
  Requires-Dist: pycryptodome>=3.20
20
11
  Requires-Dist: grpcio>=1.49.1
21
12
  Requires-Dist: brotli>=1.1
22
- Provides-Extra: test
23
- Requires-Dist: pytest>=8; extra == "test"
24
- Requires-Dist: pyflakes>=3.2; extra == "test"
25
- Requires-Dist: jsonschema>=4.18; extra == "test"
13
+ Requires-Dist: fonttools>=4.50 ; extra == 'fonts'
14
+ Requires-Dist: freetype-py>=2.5 ; extra == 'fonts'
15
+ Requires-Dist: scipy>=1.13 ; extra == 'fonts'
16
+ Requires-Dist: pytest>=8 ; extra == 'test'
17
+ Requires-Dist: pyflakes>=3.2 ; extra == 'test'
18
+ Requires-Dist: jsonschema>=4.18 ; extra == 'test'
26
19
  Provides-Extra: fonts
27
- Requires-Dist: fonttools>=4.50; extra == "fonts"
28
- Requires-Dist: freetype-py>=2.5; extra == "fonts"
29
- Requires-Dist: scipy>=1.13; extra == "fonts"
30
- Dynamic: license-file
20
+ Provides-Extra: test
21
+ License-File: LICENSE
22
+ Summary: Offline data toolkit for BanG Dream! Our Notes game files: catalogs, bundles, master data, stories, Live2D, audio and live charts
23
+ Author: MetaMiku, emptysekai
24
+ License-Expression: MIT
25
+ Requires-Python: >=3.11
26
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
27
+ Project-URL: Homepage, https://github.com/MetaSekaiLab/nnnotes
28
+ Project-URL: Issues, https://github.com/MetaSekaiLab/nnnotes/issues
29
+ Project-URL: Source, https://github.com/MetaSekaiLab/nnnotes
31
30
 
32
31
  # nnnotes?!
33
32
 
33
+ Japanese-release support includes anonymous version discovery, authenticated CDN downloads, gzip catalogs,
34
+ snapshot-isolated caches and split APKs. See the [JP guide](https://github.com/MetaSekaiLab/nnnotes/blob/main/docs/jp.md)
35
+ for configuration and validation limits.
36
+
34
37
  [简体中文](https://github.com/MetaSekaiLab/nnnotes/blob/main/README.md) | [English](https://github.com/MetaSekaiLab/nnnotes/blob/main/README.en.md)
35
38
 
36
39
  nnnotes is an offline data toolkit for the game files of BanG Dream! Our Notes: it reads Addressables catalogs,
@@ -78,9 +81,10 @@ nnnotes config check # each setting's origin and whether it is valid
78
81
  | `audio` | CRI cue sheet | one audio file per cue (FLAC / Ogg / WAV) + cue metadata |
79
82
  | `crikey` | APK | the CRI HCA keycode from the game's boot data (shows whether it was found; can write a `.hcakey`) |
80
83
  | `player` | APK | render-related global settings (color space, quality levels, renderers) as JSON |
84
+ | `ui` (experimental) | APK set and its embedded catalog | an offline serialized prefab library with dependency roots, AnimatorControllers, stable references and PNG/font resources for the optional ournotes-player UI preview ([format and limits](https://github.com/MetaSekaiLab/nnnotes/blob/main/docs/ui.md)) |
81
85
  | `live` | music ID + difficulty | a full chart directory: chart and runtime notes, 3D scene, note and effect assets, BGM and sounds, sound routing |
82
86
  | `web` | `--pair music:difficulty` (repeatable) or `--all`; `--live2d model` (repeatable) or `--all-live2d`; `--story episode` (repeatable) or `--all-stories`; `--region region` (repeatable) or `--all-regions` | an ournotes-player static site: shared player + per-chart / per-model / per-episode manifests + content-addressed assets; the Live2D models of the stories are built first as models, listed in `models.json`, and the story manifests reference them; one site can serve several regions, with listing texts in five languages; a story's interface texts are grouped by language, with TextMeshPro font assets generated from open fonts (the game's fonts with `--fonts game`); compressible assets (JSON, shaders, moc3, ...) are stored gzip encoded by default (`--compress br` / `none`) |
83
- | `deck-data` | master data files (`--master-files` directory or `--apk-master`) | one JSON file for deck-building tools: every chart's runtime notes, skill events and fever ranges, and the master data tables about cards, skills, bonuses, scores and events ([format](https://github.com/MetaSekaiLab/nnnotes/blob/main/docs/deck-data.md)) |
87
+ | `music-data` | master data files (`--master-files` directory or `--apk-master`), or decoded master data with its manifest (`--decoded-master`) | one JSON file with every song and chart: titles and credits in five languages, bands, vocal characters, category, tags, release time, score ranks, BGM length; the Gekisou catalog (member cards, snaps and their Gekisou skills and support skills); per difficulty the level, note counts, BPM, chart times, skill events and fever ranges; and the chart statistics the deck model ournotes-deck (built into nnnotes) measures on its whole-live simulation (the no-skill score, the weight of every score-up skill kind at every position), plus single-shape Gekisou aptitude (increments and standard errors, not an optimal deck; omit with `--no-gekisou-aptitude`). `--full` adds the deck model's input: every chart's runtime notes and the master data tables about cards, skills, bonuses, scores and events ([format](https://github.com/MetaSekaiLab/nnnotes/blob/main/docs/music-data.md)) |
84
88
 
85
89
  Export conventions:
86
90
 
@@ -105,13 +109,13 @@ Based on all data of the Taiwan server, version 1.0.1 (zh-Hant):
105
109
  | catalog / bundle decryption / dependency closure | working |
106
110
  | master data decoding | working |
107
111
  | stories, `adv` | 946 / 946 episodes |
108
- | stories, `story` (full directory) | 946 / 946 episodes have all their resources in the catalog and of supported kinds (the resource closure equals the game's own per-episode download list); 759 exported one by one, the other 187 (with frames, effects, post effects, stills and the like) not yet one by one |
112
+ | stories, `story` (full directory) | 946 / 946 episodes have all their resources in the catalog and of supported kinds (the resource closure equals the game's own per-episode download list); 946 / 946 exported and checked one by one: the files, models and audio the output refers to exist, every JSON parses and every path it refers to resolves; the 235 episodes with frames, effects, post effects, stills, chats, talk windows or videos are byte-identical across two exports except for the models directory path |
109
113
  | Live2D models | 239 / 239 (every model of the catalog; episodes use 185 of them) |
110
114
  | CRI audio | 681 / 681 cue sheets |
111
115
  | charts, `live` | 336 / 336 (music, difficulty) pairs |
112
- | web site, `web` | 336 / 336 charts, 239 / 239 Live2D models, 946 / 946 story episodes (two full builds in English without audio are byte-identical and 946 / 946 pass the data validation; a 27-episode sample in five languages with AAC audio is likewise identical across two builds and valid) |
113
- | spots, `spot` / `room` | one spot verified, the others not individually checked |
114
- | other regions (en / kr) and languages | checked: the regions serve the same catalog for a language and the same bundles, and the keys are shared; the master tables the charts use are the same in the three regions, and the text tables have all five languages; the chart exports checked match the Taiwan server's. A full multi-region site build is not verified yet |
116
+ | web site, `web` | 336 / 336 charts, 239 / 239 Live2D models, 946 / 946 story episodes (two full builds in English without audio are byte-identical and 946 / 946 pass the data validation); two full builds of the three regions in five languages with AAC audio (`--all-regions`, with the live master data: 340 charts of 85 musics) are likewise byte-identical and pass the data validation in full |
117
+ | spots, `spot` / `room` | 39 / 39 spots, every reference resolves; `room` exports 20 / 20 room backgrounds of the catalog. Every glb passes the Khronos glTF-Validator with 0 errors |
118
+ | other regions (en / kr) and languages | checked: the regions serve the same catalog for a language and the same bundles, and the keys are shared; the master tables the charts use are the same in the three regions, and the text tables have all five languages; the chart exports checked match the Taiwan server's. A full multi-region site build is verified (the row above) |
115
119
 
116
120
  ## Requirements
117
121
 
@@ -220,3 +224,4 @@ See [docs/performance.md](https://github.com/MetaSekaiLab/nnnotes/blob/main/docs
220
224
  ## License
221
225
 
222
226
  MIT, see [LICENSE](https://github.com/MetaSekaiLab/nnnotes/blob/main/LICENSE).
227
+
@@ -1,36 +1,9 @@
1
- Metadata-Version: 2.4
2
- Name: nnnotes
3
- Version: 0.1.1
4
- Summary: Offline data toolkit for BanG Dream! Our Notes game files: catalogs, bundles, master data, stories, Live2D, audio and live charts
5
- Author: MetaMiku, emptysekai
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/MetaSekaiLab/nnnotes
8
- Project-URL: Source, https://github.com/MetaSekaiLab/nnnotes
9
- Project-URL: Issues, https://github.com/MetaSekaiLab/nnnotes/issues
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Operating System :: OS Independent
12
- Classifier: Topic :: Multimedia
13
- Requires-Python: >=3.11
14
- Description-Content-Type: text/markdown
15
- License-File: LICENSE
16
- Requires-Dist: UnityPy>=1.25
17
- Requires-Dist: numpy>=2.0
18
- Requires-Dist: Pillow>=10.0
19
- Requires-Dist: pycryptodome>=3.20
20
- Requires-Dist: grpcio>=1.49.1
21
- Requires-Dist: brotli>=1.1
22
- Provides-Extra: test
23
- Requires-Dist: pytest>=8; extra == "test"
24
- Requires-Dist: pyflakes>=3.2; extra == "test"
25
- Requires-Dist: jsonschema>=4.18; extra == "test"
26
- Provides-Extra: fonts
27
- Requires-Dist: fonttools>=4.50; extra == "fonts"
28
- Requires-Dist: freetype-py>=2.5; extra == "fonts"
29
- Requires-Dist: scipy>=1.13; extra == "fonts"
30
- Dynamic: license-file
31
-
32
1
  # nnnotes?!
33
2
 
3
+ Japanese-release support includes anonymous version discovery, authenticated CDN downloads, gzip catalogs,
4
+ snapshot-isolated caches and split APKs. See the [JP guide](https://github.com/MetaSekaiLab/nnnotes/blob/main/docs/jp.md)
5
+ for configuration and validation limits.
6
+
34
7
  [简体中文](https://github.com/MetaSekaiLab/nnnotes/blob/main/README.md) | [English](https://github.com/MetaSekaiLab/nnnotes/blob/main/README.en.md)
35
8
 
36
9
  nnnotes is an offline data toolkit for the game files of BanG Dream! Our Notes: it reads Addressables catalogs,
@@ -78,9 +51,10 @@ nnnotes config check # each setting's origin and whether it is valid
78
51
  | `audio` | CRI cue sheet | one audio file per cue (FLAC / Ogg / WAV) + cue metadata |
79
52
  | `crikey` | APK | the CRI HCA keycode from the game's boot data (shows whether it was found; can write a `.hcakey`) |
80
53
  | `player` | APK | render-related global settings (color space, quality levels, renderers) as JSON |
54
+ | `ui` (experimental) | APK set and its embedded catalog | an offline serialized prefab library with dependency roots, AnimatorControllers, stable references and PNG/font resources for the optional ournotes-player UI preview ([format and limits](https://github.com/MetaSekaiLab/nnnotes/blob/main/docs/ui.md)) |
81
55
  | `live` | music ID + difficulty | a full chart directory: chart and runtime notes, 3D scene, note and effect assets, BGM and sounds, sound routing |
82
56
  | `web` | `--pair music:difficulty` (repeatable) or `--all`; `--live2d model` (repeatable) or `--all-live2d`; `--story episode` (repeatable) or `--all-stories`; `--region region` (repeatable) or `--all-regions` | an ournotes-player static site: shared player + per-chart / per-model / per-episode manifests + content-addressed assets; the Live2D models of the stories are built first as models, listed in `models.json`, and the story manifests reference them; one site can serve several regions, with listing texts in five languages; a story's interface texts are grouped by language, with TextMeshPro font assets generated from open fonts (the game's fonts with `--fonts game`); compressible assets (JSON, shaders, moc3, ...) are stored gzip encoded by default (`--compress br` / `none`) |
83
- | `deck-data` | master data files (`--master-files` directory or `--apk-master`) | one JSON file for deck-building tools: every chart's runtime notes, skill events and fever ranges, and the master data tables about cards, skills, bonuses, scores and events ([format](https://github.com/MetaSekaiLab/nnnotes/blob/main/docs/deck-data.md)) |
57
+ | `music-data` | master data files (`--master-files` directory or `--apk-master`), or decoded master data with its manifest (`--decoded-master`) | one JSON file with every song and chart: titles and credits in five languages, bands, vocal characters, category, tags, release time, score ranks, BGM length; the Gekisou catalog (member cards, snaps and their Gekisou skills and support skills); per difficulty the level, note counts, BPM, chart times, skill events and fever ranges; and the chart statistics the deck model ournotes-deck (built into nnnotes) measures on its whole-live simulation (the no-skill score, the weight of every score-up skill kind at every position), plus single-shape Gekisou aptitude (increments and standard errors, not an optimal deck; omit with `--no-gekisou-aptitude`). `--full` adds the deck model's input: every chart's runtime notes and the master data tables about cards, skills, bonuses, scores and events ([format](https://github.com/MetaSekaiLab/nnnotes/blob/main/docs/music-data.md)) |
84
58
 
85
59
  Export conventions:
86
60
 
@@ -105,13 +79,13 @@ Based on all data of the Taiwan server, version 1.0.1 (zh-Hant):
105
79
  | catalog / bundle decryption / dependency closure | working |
106
80
  | master data decoding | working |
107
81
  | stories, `adv` | 946 / 946 episodes |
108
- | stories, `story` (full directory) | 946 / 946 episodes have all their resources in the catalog and of supported kinds (the resource closure equals the game's own per-episode download list); 759 exported one by one, the other 187 (with frames, effects, post effects, stills and the like) not yet one by one |
82
+ | stories, `story` (full directory) | 946 / 946 episodes have all their resources in the catalog and of supported kinds (the resource closure equals the game's own per-episode download list); 946 / 946 exported and checked one by one: the files, models and audio the output refers to exist, every JSON parses and every path it refers to resolves; the 235 episodes with frames, effects, post effects, stills, chats, talk windows or videos are byte-identical across two exports except for the models directory path |
109
83
  | Live2D models | 239 / 239 (every model of the catalog; episodes use 185 of them) |
110
84
  | CRI audio | 681 / 681 cue sheets |
111
85
  | charts, `live` | 336 / 336 (music, difficulty) pairs |
112
- | web site, `web` | 336 / 336 charts, 239 / 239 Live2D models, 946 / 946 story episodes (two full builds in English without audio are byte-identical and 946 / 946 pass the data validation; a 27-episode sample in five languages with AAC audio is likewise identical across two builds and valid) |
113
- | spots, `spot` / `room` | one spot verified, the others not individually checked |
114
- | other regions (en / kr) and languages | checked: the regions serve the same catalog for a language and the same bundles, and the keys are shared; the master tables the charts use are the same in the three regions, and the text tables have all five languages; the chart exports checked match the Taiwan server's. A full multi-region site build is not verified yet |
86
+ | web site, `web` | 336 / 336 charts, 239 / 239 Live2D models, 946 / 946 story episodes (two full builds in English without audio are byte-identical and 946 / 946 pass the data validation); two full builds of the three regions in five languages with AAC audio (`--all-regions`, with the live master data: 340 charts of 85 musics) are likewise byte-identical and pass the data validation in full |
87
+ | spots, `spot` / `room` | 39 / 39 spots, every reference resolves; `room` exports 20 / 20 room backgrounds of the catalog. Every glb passes the Khronos glTF-Validator with 0 errors |
88
+ | other regions (en / kr) and languages | checked: the regions serve the same catalog for a language and the same bundles, and the keys are shared; the master tables the charts use are the same in the three regions, and the text tables have all five languages; the chart exports checked match the Taiwan server's. A full multi-region site build is verified (the row above) |
115
89
 
116
90
  ## Requirements
117
91
 
@@ -1,6 +1,6 @@
1
1
  [build-system]
2
- requires = ["setuptools>=77"]
3
- build-backend = "setuptools.build_meta"
2
+ requires = ["maturin>=1.9,<2"]
3
+ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "nnnotes"
@@ -37,14 +37,14 @@ Homepage = "https://github.com/MetaSekaiLab/nnnotes"
37
37
  Source = "https://github.com/MetaSekaiLab/nnnotes"
38
38
  Issues = "https://github.com/MetaSekaiLab/nnnotes/issues"
39
39
 
40
- [tool.setuptools.dynamic]
41
- version = { attr = "nnnotes.__version__" }
42
-
43
- [tool.setuptools.packages.find]
44
- where = ["src"]
45
-
46
- [tool.setuptools.package-data]
47
- nnnotes = ["typetrees/*.json", "viewrules.json", "nnnotes.example.toml"]
40
+ [tool.maturin]
41
+ # the pure Python package src/nnnotes with the extension module nnnotes._deck (rust/: ournotes-deck, pinned by
42
+ # commit); the version is rust/Cargo.toml's, kept equal to nnnotes.__version__ (tests/test_musicdata.py)
43
+ python-source = "src"
44
+ module-name = "nnnotes._deck"
45
+ manifest-path = "rust/Cargo.toml"
46
+ profile = "release"
47
+ locked = true
48
48
 
49
49
  [tool.pytest.ini_options]
50
50
  testpaths = ["tests"]
@@ -0,0 +1,215 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "heck"
7
+ version = "0.5.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
10
+
11
+ [[package]]
12
+ name = "itoa"
13
+ version = "1.0.18"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
16
+
17
+ [[package]]
18
+ name = "libc"
19
+ version = "0.2.189"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
22
+
23
+ [[package]]
24
+ name = "memchr"
25
+ version = "2.8.3"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
28
+
29
+ [[package]]
30
+ name = "nnnotes-deck"
31
+ version = "0.1.3"
32
+ dependencies = [
33
+ "ournotes-deck",
34
+ "pyo3",
35
+ "serde_json",
36
+ ]
37
+
38
+ [[package]]
39
+ name = "once_cell"
40
+ version = "1.21.4"
41
+ source = "registry+https://github.com/rust-lang/crates.io-index"
42
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
43
+
44
+ [[package]]
45
+ name = "ournotes-deck"
46
+ version = "0.0.1"
47
+ source = "git+https://github.com/empty-sekai/ournotes-deck?rev=dbd9cf01a4854808dceb6aedcd4041af372faeee#dbd9cf01a4854808dceb6aedcd4041af372faeee"
48
+ dependencies = [
49
+ "serde",
50
+ "serde_json",
51
+ ]
52
+
53
+ [[package]]
54
+ name = "portable-atomic"
55
+ version = "1.15.0"
56
+ source = "registry+https://github.com/rust-lang/crates.io-index"
57
+ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
58
+
59
+ [[package]]
60
+ name = "proc-macro2"
61
+ version = "1.0.107"
62
+ source = "registry+https://github.com/rust-lang/crates.io-index"
63
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
64
+ dependencies = [
65
+ "unicode-ident",
66
+ ]
67
+
68
+ [[package]]
69
+ name = "pyo3"
70
+ version = "0.29.2"
71
+ source = "registry+https://github.com/rust-lang/crates.io-index"
72
+ checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
73
+ dependencies = [
74
+ "libc",
75
+ "once_cell",
76
+ "portable-atomic",
77
+ "pyo3-build-config",
78
+ "pyo3-ffi",
79
+ "pyo3-macros",
80
+ ]
81
+
82
+ [[package]]
83
+ name = "pyo3-build-config"
84
+ version = "0.29.2"
85
+ source = "registry+https://github.com/rust-lang/crates.io-index"
86
+ checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
87
+ dependencies = [
88
+ "target-lexicon",
89
+ ]
90
+
91
+ [[package]]
92
+ name = "pyo3-ffi"
93
+ version = "0.29.2"
94
+ source = "registry+https://github.com/rust-lang/crates.io-index"
95
+ checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
96
+ dependencies = [
97
+ "libc",
98
+ "pyo3-build-config",
99
+ ]
100
+
101
+ [[package]]
102
+ name = "pyo3-macros"
103
+ version = "0.29.2"
104
+ source = "registry+https://github.com/rust-lang/crates.io-index"
105
+ checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
106
+ dependencies = [
107
+ "proc-macro2",
108
+ "pyo3-macros-backend",
109
+ "quote",
110
+ "syn 2.0.119",
111
+ ]
112
+
113
+ [[package]]
114
+ name = "pyo3-macros-backend"
115
+ version = "0.29.2"
116
+ source = "registry+https://github.com/rust-lang/crates.io-index"
117
+ checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
118
+ dependencies = [
119
+ "heck",
120
+ "proc-macro2",
121
+ "quote",
122
+ "syn 2.0.119",
123
+ ]
124
+
125
+ [[package]]
126
+ name = "quote"
127
+ version = "1.0.47"
128
+ source = "registry+https://github.com/rust-lang/crates.io-index"
129
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
130
+ dependencies = [
131
+ "proc-macro2",
132
+ ]
133
+
134
+ [[package]]
135
+ name = "serde"
136
+ version = "1.0.229"
137
+ source = "registry+https://github.com/rust-lang/crates.io-index"
138
+ checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
139
+ dependencies = [
140
+ "serde_core",
141
+ "serde_derive",
142
+ ]
143
+
144
+ [[package]]
145
+ name = "serde_core"
146
+ version = "1.0.229"
147
+ source = "registry+https://github.com/rust-lang/crates.io-index"
148
+ checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
149
+ dependencies = [
150
+ "serde_derive",
151
+ ]
152
+
153
+ [[package]]
154
+ name = "serde_derive"
155
+ version = "1.0.229"
156
+ source = "registry+https://github.com/rust-lang/crates.io-index"
157
+ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
158
+ dependencies = [
159
+ "proc-macro2",
160
+ "quote",
161
+ "syn 3.0.6",
162
+ ]
163
+
164
+ [[package]]
165
+ name = "serde_json"
166
+ version = "1.0.151"
167
+ source = "registry+https://github.com/rust-lang/crates.io-index"
168
+ checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
169
+ dependencies = [
170
+ "itoa",
171
+ "memchr",
172
+ "serde",
173
+ "serde_core",
174
+ "zmij",
175
+ ]
176
+
177
+ [[package]]
178
+ name = "syn"
179
+ version = "2.0.119"
180
+ source = "registry+https://github.com/rust-lang/crates.io-index"
181
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
182
+ dependencies = [
183
+ "proc-macro2",
184
+ "quote",
185
+ "unicode-ident",
186
+ ]
187
+
188
+ [[package]]
189
+ name = "syn"
190
+ version = "3.0.6"
191
+ source = "registry+https://github.com/rust-lang/crates.io-index"
192
+ checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
193
+ dependencies = [
194
+ "proc-macro2",
195
+ "quote",
196
+ "unicode-ident",
197
+ ]
198
+
199
+ [[package]]
200
+ name = "target-lexicon"
201
+ version = "0.13.5"
202
+ source = "registry+https://github.com/rust-lang/crates.io-index"
203
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
204
+
205
+ [[package]]
206
+ name = "unicode-ident"
207
+ version = "1.0.26"
208
+ source = "registry+https://github.com/rust-lang/crates.io-index"
209
+ checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
210
+
211
+ [[package]]
212
+ name = "zmij"
213
+ version = "1.0.23"
214
+ source = "registry+https://github.com/rust-lang/crates.io-index"
215
+ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
@@ -0,0 +1,23 @@
1
+ [package]
2
+ name = "nnnotes-deck"
3
+ # the nnnotes version (src/nnnotes/__init__.py); the wheel takes its version from here
4
+ version = "0.1.3"
5
+ edition = "2024"
6
+ rust-version = "1.85"
7
+ description = "nnnotes._deck: the ournotes-deck chart statistics as a Python extension module"
8
+ license = "MIT"
9
+ publish = false
10
+
11
+ [lib]
12
+ name = "_deck"
13
+ crate-type = ["cdylib"]
14
+
15
+ [dependencies]
16
+ pyo3 = { version = "0.29", features = ["abi3-py311"] }
17
+ # pinned by commit; .github/workflows/deck.yml opens a pull request when ournotes-deck moves
18
+ ournotes-deck = { git = "https://github.com/empty-sekai/ournotes-deck", rev = "dbd9cf01a4854808dceb6aedcd4041af372faeee" }
19
+ serde_json = { version = "1", features = ["raw_value", "arbitrary_precision"] }
20
+
21
+ [profile.release]
22
+ lto = "thin"
23
+ codegen-units = 1
@@ -0,0 +1,33 @@
1
+ //! Records the ournotes-deck package the module is built with (version and git commit, from Cargo.lock) as the
2
+ //! environment variables DECK_VERSION, DECK_SOURCE and DECK_COMMIT of the crate.
3
+
4
+ use std::path::Path;
5
+
6
+ fn main() {
7
+ let dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
8
+ let lock = Path::new(&dir).join("Cargo.lock");
9
+ println!("cargo:rerun-if-changed={}", lock.display());
10
+ let text = std::fs::read_to_string(&lock).expect("rust/Cargo.lock (the deck commit is read from it)");
11
+ let (mut version, mut source) = (None, None);
12
+ for block in text.split("[[package]]") {
13
+ let field = |k: &str| {
14
+ block.lines().find_map(|l| {
15
+ let v = l.trim().strip_prefix(k)?.trim_start().strip_prefix('=')?.trim();
16
+ Some(v.trim_matches('"').to_string())
17
+ })
18
+ };
19
+ if field("name").as_deref() == Some("ournotes-deck") {
20
+ version = field("version");
21
+ source = field("source");
22
+ }
23
+ }
24
+ let version = version.expect("Cargo.lock has no ournotes-deck package");
25
+ let source = source.expect("ournotes-deck has no source in Cargo.lock");
26
+ // git+https://github.com/empty-sekai/ournotes-deck?rev=<rev>#<commit>
27
+ let (url, commit) = source.split_once('#').expect("ournotes-deck is not a git dependency");
28
+ let url = url.strip_prefix("git+").unwrap_or(url);
29
+ let url = url.split_once('?').map_or(url, |(u, _)| u);
30
+ println!("cargo:rustc-env=DECK_VERSION={version}");
31
+ println!("cargo:rustc-env=DECK_SOURCE={url}");
32
+ println!("cargo:rustc-env=DECK_COMMIT={commit}");
33
+ }
@@ -0,0 +1,2 @@
1
+ max_width = 120
2
+ use_small_heuristics = "Max"
@@ -0,0 +1,116 @@
1
+ //! `nnnotes._deck`: the chart statistics of ournotes-deck (`ournotes-deck.chart-stats/2`) on a deck data document
2
+ //! held in memory, the charts measured in parallel.
3
+
4
+ use std::sync::Mutex;
5
+ use std::sync::atomic::{AtomicUsize, Ordering};
6
+
7
+ use ournotes_deck::chartstats::{self, AptitudeOptions, ChartStats, GEKISOU_SEEDS, Options};
8
+ use ournotes_deck::data::DeckData;
9
+ use ournotes_deck::error::Error;
10
+ use pyo3::exceptions::PyValueError;
11
+ use pyo3::prelude::*;
12
+ use pyo3::types::PyDict;
13
+
14
+ fn value_error(e: impl std::fmt::Display) -> PyErr {
15
+ PyValueError::new_err(e.to_string())
16
+ }
17
+
18
+ /// The statistics of every chart, in chart order, on `workers` threads.
19
+ fn measure(data: &DeckData, options: &Options, workers: usize) -> Result<Vec<ChartStats>, Error> {
20
+ let kinds = chartstats::kinds(&data.master);
21
+ let n = data.charts.len();
22
+ let next = AtomicUsize::new(0);
23
+ let out: Mutex<Vec<Option<Result<ChartStats, Error>>>> = Mutex::new((0..n).map(|_| None).collect());
24
+ std::thread::scope(|s| {
25
+ for _ in 0..workers.clamp(1, n.max(1)) {
26
+ s.spawn(|| {
27
+ loop {
28
+ let i = next.fetch_add(1, Ordering::Relaxed);
29
+ if i >= n {
30
+ break;
31
+ }
32
+ let c = &data.charts[i];
33
+ // as chartstats::document names a chart's domain errors
34
+ let r = chartstats::chart_stats_with(&data.master, c, &kinds, options).map_err(|e| match e {
35
+ Error::Domain(m) => Error::Domain(format!("chart {}: {m}", c.score_id)),
36
+ e => e,
37
+ });
38
+ let failed = r.is_err();
39
+ out.lock().expect("results")[i] = Some(r);
40
+ if failed {
41
+ next.store(n, Ordering::Relaxed);
42
+ }
43
+ }
44
+ });
45
+ }
46
+ });
47
+ // charts are taken in order and a taken chart always completes, so every chart before the first failure has
48
+ // its result: the error returned is the first in chart order, as the command's
49
+ let mut stats = Vec::with_capacity(n);
50
+ for r in out.into_inner().expect("results") {
51
+ stats.push(r.expect("a chart before the first failure has run")?);
52
+ }
53
+ Ok(stats)
54
+ }
55
+
56
+ /// chart_stats(data, seeds=None, workers=None, aptitude=True, aptitude_max_seeds=None,
57
+ /// aptitude_cross_seeds=None) -> str
58
+ ///
59
+ /// The `ournotes-deck.chart-stats/2` document (JSON text) of a deck data document (`nnnotes.deck-data/1` JSON text),
60
+ /// as `ournotes-deck chart-stats` writes it. `seeds`: the size of the seed set of charts with a luck range (default
61
+ /// 8); `workers`: threads measuring charts (default: the available parallelism). Raises ValueError for data the
62
+ /// deck model cannot read or a chart whose check deck fails. `aptitude`: measure single Gekisou skill shapes
63
+ /// (default true); `aptitude_max_seeds` and `aptitude_cross_seeds`: sample caps (defaults 65536 and 64).
64
+ #[pyfunction]
65
+ #[pyo3(signature = (data, seeds=None, workers=None, aptitude=true, aptitude_max_seeds=None, aptitude_cross_seeds=None))]
66
+ fn chart_stats(
67
+ py: Python<'_>,
68
+ data: &str,
69
+ seeds: Option<usize>,
70
+ workers: Option<usize>,
71
+ aptitude: bool,
72
+ aptitude_max_seeds: Option<usize>,
73
+ aptitude_cross_seeds: Option<usize>,
74
+ ) -> PyResult<String> {
75
+ let seeds = seeds.unwrap_or(GEKISOU_SEEDS);
76
+ let defaults = AptitudeOptions::default();
77
+ let options = Options {
78
+ seeds,
79
+ aptitude: aptitude.then_some(AptitudeOptions {
80
+ max_seeds: aptitude_max_seeds.unwrap_or(defaults.max_seeds),
81
+ cross_seeds: aptitude_cross_seeds.unwrap_or(defaults.cross_seeds),
82
+ }),
83
+ };
84
+ let workers = workers.unwrap_or_else(|| std::thread::available_parallelism().map_or(1, |n| n.get()));
85
+ py.detach(|| {
86
+ let mut data = DeckData::from_json(data).map_err(value_error)?;
87
+ let charts = std::mem::take(&mut data.charts);
88
+ // the document of no chart: the format, source, model and kinds, exactly as the command writes them
89
+ let mut doc = chartstats::document_with(&data, &options).map_err(value_error)?;
90
+ data.charts = charts;
91
+ let stats = measure(&data, &options, workers).map_err(value_error)?;
92
+ doc["charts"] = serde_json::to_value(stats).map_err(value_error)?;
93
+ serde_json::to_string(&doc).map_err(value_error)
94
+ })
95
+ }
96
+
97
+ /// info() -> dict: the ournotes-deck package of this module (name, version, source, commit) and the formats it reads
98
+ /// and writes.
99
+ #[pyfunction]
100
+ fn info(py: Python<'_>) -> PyResult<Bound<'_, PyDict>> {
101
+ let d = PyDict::new(py);
102
+ d.set_item("name", "ournotes-deck")?;
103
+ d.set_item("version", env!("DECK_VERSION"))?;
104
+ d.set_item("source", env!("DECK_SOURCE"))?;
105
+ d.set_item("commit", env!("DECK_COMMIT"))?;
106
+ d.set_item("dataFormat", ournotes_deck::data::FORMAT)?;
107
+ d.set_item("format", chartstats::FORMAT)?;
108
+ Ok(d)
109
+ }
110
+
111
+ #[pymodule]
112
+ fn _deck(m: &Bound<'_, PyModule>) -> PyResult<()> {
113
+ m.add_function(wrap_pyfunction!(chart_stats, m)?)?;
114
+ m.add_function(wrap_pyfunction!(info, m)?)?;
115
+ Ok(())
116
+ }
@@ -4,4 +4,4 @@ Reads Addressables catalogs, fetches and decrypts asset bundles, decodes master
4
4
  models, spots, shaders, CRI audio and live charts as JSON and common file formats. Game files, keys and server
5
5
  addresses are supplied by the user through the configuration; nothing of the game is included.
6
6
  """
7
- __version__ = "0.1.1"
7
+ __version__ = "0.1.3"