pycapcut 0.0.1__py3-none-any.whl

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 (88) hide show
  1. pyJianYingDraft/__init__.py +276 -0
  2. pyJianYingDraft/animation.py +140 -0
  3. pyJianYingDraft/assets/__init__.py +45 -0
  4. pyJianYingDraft/assets/draft_content_template.json +125 -0
  5. pyJianYingDraft/assets/draft_meta_info.json +67 -0
  6. pyJianYingDraft/audio_segment.py +225 -0
  7. pyJianYingDraft/draft_content_template.json +119 -0
  8. pyJianYingDraft/draft_folder.py +153 -0
  9. pyJianYingDraft/effect_segment.py +36 -0
  10. pyJianYingDraft/exceptions.py +23 -0
  11. pyJianYingDraft/jianying_controller.py +230 -0
  12. pyJianYingDraft/keyframe.py +94 -0
  13. pyJianYingDraft/local_materials.py +198 -0
  14. pyJianYingDraft/metadata/__init__.py +51 -0
  15. pyJianYingDraft/metadata/animation_meta.py +689 -0
  16. pyJianYingDraft/metadata/audio_effect_meta.py +367 -0
  17. pyJianYingDraft/metadata/audio_scene_effect.py +252 -0
  18. pyJianYingDraft/metadata/effect_meta.py +191 -0
  19. pyJianYingDraft/metadata/filter_meta.py +603 -0
  20. pyJianYingDraft/metadata/font_meta.py +341 -0
  21. pyJianYingDraft/metadata/mask_meta.py +15 -0
  22. pyJianYingDraft/metadata/speech_to_song.py +12 -0
  23. pyJianYingDraft/metadata/text_intro.py +152 -0
  24. pyJianYingDraft/metadata/text_loop.py +105 -0
  25. pyJianYingDraft/metadata/text_outro.py +105 -0
  26. pyJianYingDraft/metadata/tone_effect.py +107 -0
  27. pyJianYingDraft/metadata/transition_meta.py +733 -0
  28. pyJianYingDraft/metadata/video_character_effect.py +1913 -0
  29. pyJianYingDraft/metadata/video_effect_meta.py +9619 -0
  30. pyJianYingDraft/metadata/video_group_animation.py +131 -0
  31. pyJianYingDraft/metadata/video_intro.py +103 -0
  32. pyJianYingDraft/metadata/video_outro.py +80 -0
  33. pyJianYingDraft/metadata/video_scene_effect.py +7706 -0
  34. pyJianYingDraft/script_file.py +808 -0
  35. pyJianYingDraft/segment.py +256 -0
  36. pyJianYingDraft/template_mode.py +221 -0
  37. pyJianYingDraft/text_segment.py +436 -0
  38. pyJianYingDraft/time_util.py +99 -0
  39. pyJianYingDraft/track.py +138 -0
  40. pyJianYingDraft/util.py +58 -0
  41. pyJianYingDraft/video_segment.py +526 -0
  42. pycapcut/__init__.py +276 -0
  43. pycapcut/animation.py +140 -0
  44. pycapcut/assets/__init__.py +45 -0
  45. pycapcut/assets/draft_content_template.json +132 -0
  46. pycapcut/assets/draft_meta_info.json +73 -0
  47. pycapcut/audio_segment.py +225 -0
  48. pycapcut/draft_folder.py +153 -0
  49. pycapcut/effect_segment.py +36 -0
  50. pycapcut/exceptions.py +23 -0
  51. pycapcut/jianying_controller.py +230 -0
  52. pycapcut/keyframe.py +94 -0
  53. pycapcut/local_materials.py +198 -0
  54. pycapcut/metadata/__init__.py +51 -0
  55. pycapcut/metadata/audio_scene_effect.py +252 -0
  56. pycapcut/metadata/effect_meta.py +191 -0
  57. pycapcut/metadata/filter_meta.py +603 -0
  58. pycapcut/metadata/font_meta.py +341 -0
  59. pycapcut/metadata/mask_meta.py +15 -0
  60. pycapcut/metadata/speech_to_song.py +12 -0
  61. pycapcut/metadata/text_intro.py +152 -0
  62. pycapcut/metadata/text_loop.py +105 -0
  63. pycapcut/metadata/text_outro.py +105 -0
  64. pycapcut/metadata/tone_effect.py +107 -0
  65. pycapcut/metadata/transition_meta.py +733 -0
  66. pycapcut/metadata/video_character_effect.py +1913 -0
  67. pycapcut/metadata/video_group_animation.py +131 -0
  68. pycapcut/metadata/video_intro.py +103 -0
  69. pycapcut/metadata/video_outro.py +80 -0
  70. pycapcut/metadata/video_scene_effect.py +7706 -0
  71. pycapcut/script_file.py +808 -0
  72. pycapcut/segment.py +256 -0
  73. pycapcut/template_mode.py +221 -0
  74. pycapcut/text_segment.py +436 -0
  75. pycapcut/time_util.py +99 -0
  76. pycapcut/track.py +138 -0
  77. pycapcut/util.py +58 -0
  78. pycapcut/video_segment.py +526 -0
  79. pycapcut-0.0.1.dist-info/METADATA +77 -0
  80. pycapcut-0.0.1.dist-info/RECORD +88 -0
  81. pycapcut-0.0.1.dist-info/WHEEL +5 -0
  82. pycapcut-0.0.1.dist-info/top_level.txt +1 -0
  83. tools/__init__.py +17 -0
  84. tools/build_release.py +270 -0
  85. tools/code_generator.py +247 -0
  86. tools/extract_all.py +256 -0
  87. tools/extractor_base.py +132 -0
  88. tools/jianying_extractor.py +262 -0
@@ -0,0 +1,88 @@
1
+ pyJianYingDraft/__init__.py,sha256=D-HxNjWHSZGxofVNjL5IZOWGZPYlRu9jk75Rfx5hoow,9752
2
+ pyJianYingDraft/animation.py,sha256=BBApvSU_cKL-pUGnJsl0sxrfpkt-TvZ_F6Sjm4HWqfA,5371
3
+ pyJianYingDraft/audio_segment.py,sha256=LIsHInhEdvEXzwUKEuFoyYm41j46DzkFJXHWBBAdYWY,9585
4
+ pyJianYingDraft/draft_content_template.json,sha256=WijS_Xjl9vBpVpyRqcbruxsvJJ1oDWUvFqJzN3A2ejU,3301
5
+ pyJianYingDraft/draft_folder.py,sha256=40qvONrkPeJEy8nG9snRFxCWnyGBtQab_ZNCMPfs8ZA,6017
6
+ pyJianYingDraft/effect_segment.py,sha256=rIiLPvd4WO8JnfnB6OeDI9kV8YCStVkVhKcIrFTb6E0,1298
7
+ pyJianYingDraft/exceptions.py,sha256=B5-AQlyvz2lKfSQhjj-s9lWwkuaXX-tprg6IHYtZr9g,702
8
+ pyJianYingDraft/jianying_controller.py,sha256=b4cZE6_bp8hs6PLU_iON6FfCwOPWa9xJxiWEJ8xVhz4,9709
9
+ pyJianYingDraft/keyframe.py,sha256=K2gqjutmbBBKKSGXsjQ2jRVOBO4N7-vZ2wvFKD1Rpdc,3611
10
+ pyJianYingDraft/local_materials.py,sha256=0NmEZOxnUG9skaMI1QLC8TLeGVM6wWH8KjZDIhRLbJM,7965
11
+ pyJianYingDraft/script_file.py,sha256=Ldpm9lYQAyNqBz0Pi6rQM6-rm8oDB7bc9vwU6hXqBC4,38540
12
+ pyJianYingDraft/segment.py,sha256=u78sNCpcqSQS1E2G-fMi3pjQo3BBONOu6u4hx_1R3SQ,9955
13
+ pyJianYingDraft/template_mode.py,sha256=mYzZGk5Ma3zPXPv7fHW7PUuI6Of4i09py1mXb-Bo6F4,8623
14
+ pyJianYingDraft/text_segment.py,sha256=rSKMVlnvl43y3z2wbjO78Ljmp4qgjFx8Pv9jlqgceEI,17602
15
+ pyJianYingDraft/time_util.py,sha256=eTt6suV_AlhE2o4Cuzvceq5Gw6dBPuJzerpB0_-C3xc,3226
16
+ pyJianYingDraft/track.py,sha256=jPy-DI9301r61cG_lubCihGQBCdpLfwlP9nCbAETdkc,4681
17
+ pyJianYingDraft/util.py,sha256=ALl9rZsGD3Ut0RPS_QzpGGBlC0TQnwzpa2UphWsXgFQ,2176
18
+ pyJianYingDraft/video_segment.py,sha256=GDl94Xe3FjfqQG0F1WrV-28Tsa4ZK4iy55_LCmoOIs4,22376
19
+ pyJianYingDraft/assets/__init__.py,sha256=yYumQq1KDEz1zR9pnTc8DSNQqP7KmRdXGUG2iSn-fqw,1191
20
+ pyJianYingDraft/assets/draft_content_template.json,sha256=mx-_JDqW35RYwvbQabJJByuQujOp5lcTJ5eUu2dpIE0,3440
21
+ pyJianYingDraft/assets/draft_meta_info.json,sha256=8qd273AX--xLUFrU7PQzgMIa13a2wgktHnUxb_qXFdQ,1440
22
+ pyJianYingDraft/metadata/__init__.py,sha256=gY056roOtdld9m0ByJyECsSGk7JndG6AzQv0pBcJH8k,1326
23
+ pyJianYingDraft/metadata/animation_meta.py,sha256=Q2oCKqQ3tr2sP6q5iqwtEumokviJFZTdQoQWUjdYyI8,84811
24
+ pyJianYingDraft/metadata/audio_effect_meta.py,sha256=ka4PmWdlk81nOREbqEqO3ZDdL6DEiC68GV6omf94wYQ,24295
25
+ pyJianYingDraft/metadata/audio_scene_effect.py,sha256=DZDnpBEY6W9uENTa55TYnfR2_Xj2bmWcn5GebFMUfiE,14192
26
+ pyJianYingDraft/metadata/effect_meta.py,sha256=8gW0f9LP5-ozjDFcjRju9v75_9JeQRLuEkjK1INZLaY,5791
27
+ pyJianYingDraft/metadata/filter_meta.py,sha256=BWC6TlFehsh6lJuOUPGXu1--BTocZuErb80KGto4Iwo,71735
28
+ pyJianYingDraft/metadata/font_meta.py,sha256=Bc3xSUksL8nVHSkNKc9lGiZQ9nTITbc4p1p6e3A-f-s,45580
29
+ pyJianYingDraft/metadata/mask_meta.py,sha256=4uSJJ3PI0CNTWHxJ8VaK2aA2uKcGVcXEdd22C2SmhbQ,937
30
+ pyJianYingDraft/metadata/speech_to_song.py,sha256=6qZKDVg62J5CruEY1xphrGTt2cJkZDu4t6aWJAbG5Lg,972
31
+ pyJianYingDraft/metadata/text_intro.py,sha256=SOjStWJYDEgT0Z_r6IFK4Y8tYcfEsUjB21sw5_0ajmI,19352
32
+ pyJianYingDraft/metadata/text_loop.py,sha256=tcz_ks04_dX0Ab0-ypNz1pSozuf2bciqYh051mBYQ-E,12574
33
+ pyJianYingDraft/metadata/text_outro.py,sha256=xLmYJcH0eckdPx6wfVt3e2GVUXMPIJErIeVyoLr3b48,13036
34
+ pyJianYingDraft/metadata/tone_effect.py,sha256=qoi7gdPrNvD0IDqAl13c2dgdmvhM43jJ_oUbElrxwu8,9292
35
+ pyJianYingDraft/metadata/transition_meta.py,sha256=TK_xohKvVWEk9UrSDaV-e8GKWfbK67cF_P94CrVG5nc,63944
36
+ pyJianYingDraft/metadata/video_character_effect.py,sha256=STAP1nE52zxYOHCHVIT_i6caU3dZRmiu5jNVnGlFq4M,130489
37
+ pyJianYingDraft/metadata/video_effect_meta.py,sha256=AmF6ig3bV4L2LnO1AYoRVzzcMox4sSoA-F_RD0OqocA,660895
38
+ pyJianYingDraft/metadata/video_group_animation.py,sha256=alw6jQY4GA87rmh0XmYyDOSy3zutigmOhW5pZvNVJek,16798
39
+ pyJianYingDraft/metadata/video_intro.py,sha256=lhK2PRAzFjl2jkL6h2LVpm1ibcesyozz8REKuAEyiOE,12976
40
+ pyJianYingDraft/metadata/video_outro.py,sha256=two5zzCM1Hs7smR9hduC0Wly7ZSImkCGAgjzd-f4j-Q,9844
41
+ pyJianYingDraft/metadata/video_scene_effect.py,sha256=SOsK48rhfYV96XApOTmI8vrZe1MVm8P1Re_HaKfUpq4,530418
42
+ pycapcut/__init__.py,sha256=D-HxNjWHSZGxofVNjL5IZOWGZPYlRu9jk75Rfx5hoow,9752
43
+ pycapcut/animation.py,sha256=BBApvSU_cKL-pUGnJsl0sxrfpkt-TvZ_F6Sjm4HWqfA,5371
44
+ pycapcut/audio_segment.py,sha256=LIsHInhEdvEXzwUKEuFoyYm41j46DzkFJXHWBBAdYWY,9585
45
+ pycapcut/draft_folder.py,sha256=40qvONrkPeJEy8nG9snRFxCWnyGBtQab_ZNCMPfs8ZA,6017
46
+ pycapcut/effect_segment.py,sha256=rIiLPvd4WO8JnfnB6OeDI9kV8YCStVkVhKcIrFTb6E0,1298
47
+ pycapcut/exceptions.py,sha256=B5-AQlyvz2lKfSQhjj-s9lWwkuaXX-tprg6IHYtZr9g,702
48
+ pycapcut/jianying_controller.py,sha256=b4cZE6_bp8hs6PLU_iON6FfCwOPWa9xJxiWEJ8xVhz4,9709
49
+ pycapcut/keyframe.py,sha256=K2gqjutmbBBKKSGXsjQ2jRVOBO4N7-vZ2wvFKD1Rpdc,3611
50
+ pycapcut/local_materials.py,sha256=0NmEZOxnUG9skaMI1QLC8TLeGVM6wWH8KjZDIhRLbJM,7965
51
+ pycapcut/script_file.py,sha256=Ldpm9lYQAyNqBz0Pi6rQM6-rm8oDB7bc9vwU6hXqBC4,38540
52
+ pycapcut/segment.py,sha256=u78sNCpcqSQS1E2G-fMi3pjQo3BBONOu6u4hx_1R3SQ,9955
53
+ pycapcut/template_mode.py,sha256=mYzZGk5Ma3zPXPv7fHW7PUuI6Of4i09py1mXb-Bo6F4,8623
54
+ pycapcut/text_segment.py,sha256=rSKMVlnvl43y3z2wbjO78Ljmp4qgjFx8Pv9jlqgceEI,17602
55
+ pycapcut/time_util.py,sha256=eTt6suV_AlhE2o4Cuzvceq5Gw6dBPuJzerpB0_-C3xc,3226
56
+ pycapcut/track.py,sha256=jPy-DI9301r61cG_lubCihGQBCdpLfwlP9nCbAETdkc,4681
57
+ pycapcut/util.py,sha256=ALl9rZsGD3Ut0RPS_QzpGGBlC0TQnwzpa2UphWsXgFQ,2176
58
+ pycapcut/video_segment.py,sha256=GDl94Xe3FjfqQG0F1WrV-28Tsa4ZK4iy55_LCmoOIs4,22376
59
+ pycapcut/assets/__init__.py,sha256=yYumQq1KDEz1zR9pnTc8DSNQqP7KmRdXGUG2iSn-fqw,1191
60
+ pycapcut/assets/draft_content_template.json,sha256=Jl6IKkGdhHgGwp4wqhav7OJcZlpN407L-EesOqAAeoM,3701
61
+ pycapcut/assets/draft_meta_info.json,sha256=akTMUyRBr96fWbBSnFIcetYxT68HQZLMNCaOCWCNweQ,1997
62
+ pycapcut/metadata/__init__.py,sha256=gY056roOtdld9m0ByJyECsSGk7JndG6AzQv0pBcJH8k,1326
63
+ pycapcut/metadata/audio_scene_effect.py,sha256=DZDnpBEY6W9uENTa55TYnfR2_Xj2bmWcn5GebFMUfiE,14192
64
+ pycapcut/metadata/effect_meta.py,sha256=8gW0f9LP5-ozjDFcjRju9v75_9JeQRLuEkjK1INZLaY,5791
65
+ pycapcut/metadata/filter_meta.py,sha256=BWC6TlFehsh6lJuOUPGXu1--BTocZuErb80KGto4Iwo,71735
66
+ pycapcut/metadata/font_meta.py,sha256=Bc3xSUksL8nVHSkNKc9lGiZQ9nTITbc4p1p6e3A-f-s,45580
67
+ pycapcut/metadata/mask_meta.py,sha256=4uSJJ3PI0CNTWHxJ8VaK2aA2uKcGVcXEdd22C2SmhbQ,937
68
+ pycapcut/metadata/speech_to_song.py,sha256=6qZKDVg62J5CruEY1xphrGTt2cJkZDu4t6aWJAbG5Lg,972
69
+ pycapcut/metadata/text_intro.py,sha256=SOjStWJYDEgT0Z_r6IFK4Y8tYcfEsUjB21sw5_0ajmI,19352
70
+ pycapcut/metadata/text_loop.py,sha256=tcz_ks04_dX0Ab0-ypNz1pSozuf2bciqYh051mBYQ-E,12574
71
+ pycapcut/metadata/text_outro.py,sha256=xLmYJcH0eckdPx6wfVt3e2GVUXMPIJErIeVyoLr3b48,13036
72
+ pycapcut/metadata/tone_effect.py,sha256=qoi7gdPrNvD0IDqAl13c2dgdmvhM43jJ_oUbElrxwu8,9292
73
+ pycapcut/metadata/transition_meta.py,sha256=TK_xohKvVWEk9UrSDaV-e8GKWfbK67cF_P94CrVG5nc,63944
74
+ pycapcut/metadata/video_character_effect.py,sha256=STAP1nE52zxYOHCHVIT_i6caU3dZRmiu5jNVnGlFq4M,130489
75
+ pycapcut/metadata/video_group_animation.py,sha256=alw6jQY4GA87rmh0XmYyDOSy3zutigmOhW5pZvNVJek,16798
76
+ pycapcut/metadata/video_intro.py,sha256=lhK2PRAzFjl2jkL6h2LVpm1ibcesyozz8REKuAEyiOE,12976
77
+ pycapcut/metadata/video_outro.py,sha256=two5zzCM1Hs7smR9hduC0Wly7ZSImkCGAgjzd-f4j-Q,9844
78
+ pycapcut/metadata/video_scene_effect.py,sha256=SOsK48rhfYV96XApOTmI8vrZe1MVm8P1Re_HaKfUpq4,530418
79
+ tools/__init__.py,sha256=_HsD8EymbC5PHeDrnhIu789Um4KYTwjWakSd628F4XQ,467
80
+ tools/build_release.py,sha256=FhXYRm6kU7ke_qSW_reGN-GU4Tq4et4KCPQJelWb_8A,9630
81
+ tools/code_generator.py,sha256=GbrQViYK7d1wI_VVN07sGI82mY6Cu0AW9jbyYQ0ya78,11937
82
+ tools/extract_all.py,sha256=urSSq29BRk25l6FQ1ZaY4SSEQStU7O72Hyqle-Og7ts,10640
83
+ tools/extractor_base.py,sha256=WY5Fazt87XEA1-spzOCregiXz4fbLjgkpDsnZ6to0bQ,4319
84
+ tools/jianying_extractor.py,sha256=qJySmis83iMjR-tNJXH8R9U5ns_kI8hX0PgmxXKTT8M,8730
85
+ pycapcut-0.0.1.dist-info/METADATA,sha256=8qZ9XYRT347Mbi8lwOkOe-laBsZPQVj2HXD9Y4pkicY,4368
86
+ pycapcut-0.0.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
87
+ pycapcut-0.0.1.dist-info/top_level.txt,sha256=0Y5w2_62iHAdmGngg0UXB_rwUOARnEy2IMA8e98RVVQ,9
88
+ pycapcut-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.8.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ pycapcut
tools/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ """
2
+ 剪映元数据提取工具包
3
+
4
+ 此包包含从剪映原始数据中提取和生成元数据的工具。
5
+ 这些工具仅用于内部开发,不包含在公开发布的包中。
6
+ """
7
+
8
+ from .extractor_base import MetadataExtractor, ExtractorConfig
9
+ from .jianying_extractor import JianYingExtractor
10
+ from .code_generator import CodeGenerator
11
+
12
+ __all__ = [
13
+ "MetadataExtractor",
14
+ "ExtractorConfig",
15
+ "JianYingExtractor",
16
+ "CodeGenerator"
17
+ ]
tools/build_release.py ADDED
@@ -0,0 +1,270 @@
1
+
2
+ """
3
+ 发布构建脚本
4
+
5
+ 用于构建用于公开发布的清洁版本数据
6
+ - 提取所有元数据
7
+ - 生成Python代码文件
8
+ - 创建公开数据文件
9
+ - 清理敏感信息
10
+ """
11
+
12
+ import json
13
+ import shutil
14
+ import yaml
15
+ from pathlib import Path
16
+ from typing import Dict, List, Any
17
+
18
+ from .extract_all import MetadataExtractorManager
19
+ from .code_generator import CodeGenerator
20
+
21
+
22
+ class ReleaseBuilder:
23
+ """发布版本构建器"""
24
+
25
+ def __init__(self, config_file: str = "tools/config.yaml"):
26
+ self.config_file = config_file
27
+ self.manager = MetadataExtractorManager(config_file)
28
+ self.config = self.manager.config
29
+
30
+ def build_full_release(self) -> None:
31
+ """构建完整的发布版本"""
32
+ print("=== Starting Release Build ===")
33
+
34
+ # 1. 提取所有元数据到JSON
35
+ print("\n1. Extracting metadata to JSON...")
36
+ self.manager.extract_all(legacy_format=False)
37
+
38
+ # 2. 生成Python代码文件
39
+ print("\n2. Generating Python code files...")
40
+ self._generate_all_code_files()
41
+
42
+ # 3. 创建公开数据文件
43
+ print("\n3. Creating public data file...")
44
+ self._create_public_data()
45
+
46
+ # 4. 验证生成的文件
47
+ print("\n4. Validating generated files...")
48
+ self._validate_output()
49
+
50
+ print("\n=== Release Build Completed ===")
51
+
52
+ def _generate_all_code_files(self) -> None:
53
+ """生成所有Python代码文件"""
54
+ class_mapping = self.config["code_generation"]["class_mapping"]
55
+
56
+ for data_type, mapping in class_mapping.items():
57
+ input_file = f"{self.config['data_dir']}/{data_type}.json"
58
+
59
+ if not Path(input_file).exists():
60
+ print(f"Warning: {input_file} not found, skipping...")
61
+ continue
62
+
63
+ try:
64
+ # 加载数据
65
+ with open(input_file, 'r', encoding='utf-8') as f:
66
+ data_list = json.load(f)
67
+
68
+ if not data_list:
69
+ print(f"Warning: {data_type} has no data, skipping...")
70
+ continue
71
+
72
+ import pandas as pd
73
+ data = pd.DataFrame(data_list)
74
+
75
+ # 生成代码
76
+ generator = CodeGenerator(data, data_type)
77
+ output_file = f"{self.config['output_dir']}/{mapping['file_name']}"
78
+
79
+ generator.save_to_file(
80
+ output_file,
81
+ mapping['class_name'],
82
+ mapping['base_class'],
83
+ mapping['meta_class']
84
+ )
85
+
86
+ print(f"Generated {mapping['class_name']} -> {output_file}")
87
+
88
+ except Exception as e:
89
+ print(f"Error generating code for {data_type}: {e}")
90
+
91
+ def _create_public_data(self) -> None:
92
+ """创建公开数据文件"""
93
+ public_config = self.config["release"]["public_data"]
94
+ include_fields = public_config["include_fields"]
95
+
96
+ public_data = {}
97
+ data_dir = Path(self.config['data_dir'])
98
+
99
+ for data_type, fields in include_fields.items():
100
+ input_file = data_dir / f"{data_type}.json"
101
+
102
+ if not input_file.exists():
103
+ print(f"Warning: {input_file} not found for public data")
104
+ continue
105
+
106
+ try:
107
+ with open(input_file, 'r', encoding='utf-8') as f:
108
+ full_data = json.load(f)
109
+
110
+ # 清理数据,只保留指定字段
111
+ cleaned_data = []
112
+ for item in full_data:
113
+ cleaned_item = {}
114
+ for field in fields:
115
+ if field in item:
116
+ cleaned_item[field] = item[field]
117
+ elif field == "has_params":
118
+ # 特殊处理:检查是否有参数但不暴露具体参数
119
+ cleaned_item[field] = bool(item.get("params", []))
120
+
121
+ if cleaned_item:
122
+ cleaned_data.append(cleaned_item)
123
+
124
+ public_data[data_type] = cleaned_data
125
+ print(f"Cleaned {data_type}: {len(cleaned_data)} items")
126
+
127
+ except Exception as e:
128
+ print(f"Error processing {data_type} for public data: {e}")
129
+
130
+ # 保存公开数据文件
131
+ output_path = data_dir / public_config["filename"]
132
+ with open(output_path, 'w', encoding='utf-8') as f:
133
+ json.dump(public_data, f, ensure_ascii=False, indent=2)
134
+
135
+ print(f"Public data saved to: {output_path}")
136
+
137
+ def _validate_output(self) -> None:
138
+ """验证输出文件"""
139
+ issues = []
140
+
141
+ # 检查Python代码文件
142
+ metadata_dir = Path(self.config['output_dir'])
143
+ expected_files = [
144
+ "font_meta.py",
145
+ "animation_meta.py",
146
+ "mask_meta.py",
147
+ "filter_meta.py",
148
+ "transition_meta.py",
149
+ "video_effect_meta.py",
150
+ "audio_effect_meta.py"
151
+ ]
152
+
153
+ for filename in expected_files:
154
+ filepath = metadata_dir / filename
155
+ if not filepath.exists():
156
+ issues.append(f"Missing metadata file: {filepath}")
157
+ elif filepath.stat().st_size == 0:
158
+ issues.append(f"Empty metadata file: {filepath}")
159
+
160
+ # 检查数据文件
161
+ data_dir = Path(self.config['data_dir'])
162
+ if not (data_dir / "public_metadata.json").exists():
163
+ issues.append("Missing public_metadata.json")
164
+
165
+ # 检查语法错误
166
+ for py_file in metadata_dir.glob("*.py"):
167
+ try:
168
+ with open(py_file, 'r', encoding='utf-8') as f:
169
+ content = f.read()
170
+ compile(content, str(py_file), 'exec')
171
+ except SyntaxError as e:
172
+ issues.append(f"Syntax error in {py_file}: {e}")
173
+
174
+ if issues:
175
+ print("\nValidation Issues Found:")
176
+ for issue in issues:
177
+ print(f" - {issue}")
178
+ else:
179
+ print("✅ All output files validated successfully")
180
+
181
+ def clean_for_public_release(self) -> None:
182
+ """清理项目以准备公开发布"""
183
+ print("=== Cleaning for Public Release ===")
184
+
185
+ exclude_patterns = self.config["release"]["exclude_from_public"]
186
+ cleaned_count = 0
187
+
188
+ for pattern in exclude_patterns:
189
+ for path in Path(".").glob(pattern):
190
+ if path.exists():
191
+ if path.is_dir():
192
+ shutil.rmtree(path)
193
+ else:
194
+ path.unlink()
195
+ print(f"Removed: {path}")
196
+ cleaned_count += 1
197
+
198
+ print(f"Cleaned {cleaned_count} items for public release")
199
+
200
+ def create_gitignore_update(self) -> None:
201
+ """更新.gitignore文件"""
202
+ gitignore_path = Path(".gitignore")
203
+
204
+ # 需要忽略的工具目录内容
205
+ tool_ignores = [
206
+ "",
207
+ "# Internal metadata extraction tools (not for public release)",
208
+ "tools/",
209
+ "ignored/",
210
+ "*.db",
211
+ "rp.db*",
212
+ "**/raw_data/",
213
+ "**/*_cache/",
214
+ ""
215
+ ]
216
+
217
+ # 读取现有.gitignore
218
+ existing_lines = []
219
+ if gitignore_path.exists():
220
+ with open(gitignore_path, 'r', encoding='utf-8') as f:
221
+ existing_lines = [line.strip() for line in f.readlines()]
222
+
223
+ # 检查是否需要添加新规则
224
+ needs_update = False
225
+ for ignore_rule in tool_ignores:
226
+ if ignore_rule.strip() and ignore_rule not in existing_lines:
227
+ needs_update = True
228
+ break
229
+
230
+ if needs_update:
231
+ with open(gitignore_path, 'a', encoding='utf-8') as f:
232
+ f.write('\n'.join(tool_ignores))
233
+ print("Updated .gitignore with tool exclusions")
234
+ else:
235
+ print(".gitignore already up to date")
236
+
237
+
238
+ def main():
239
+ import argparse
240
+
241
+ parser = argparse.ArgumentParser(description="构建发布版本")
242
+ parser.add_argument("--full", action="store_true", help="执行完整构建")
243
+ parser.add_argument("--code-only", action="store_true", help="只生成代码文件")
244
+ parser.add_argument("--public-data", action="store_true", help="只生成公开数据")
245
+ parser.add_argument("--clean", action="store_true", help="清理敏感文件")
246
+ parser.add_argument("--update-gitignore", action="store_true", help="更新.gitignore")
247
+ parser.add_argument("--config", default="tools/config.yaml", help="配置文件路径")
248
+
249
+ args = parser.parse_args()
250
+
251
+ if not any([args.full, args.code_only, args.public_data, args.clean, args.update_gitignore]):
252
+ parser.print_help()
253
+ return
254
+
255
+ builder = ReleaseBuilder(args.config)
256
+
257
+ if args.full:
258
+ builder.build_full_release()
259
+ elif args.code_only:
260
+ builder._generate_all_code_files()
261
+ elif args.public_data:
262
+ builder._create_public_data()
263
+ elif args.clean:
264
+ builder.clean_for_public_release()
265
+ elif args.update_gitignore:
266
+ builder.create_gitignore_update()
267
+
268
+
269
+ if __name__ == "__main__":
270
+ main()
@@ -0,0 +1,247 @@
1
+ """
2
+ 代码生成器
3
+ 用于将提取的元数据生成Python代码
4
+ """
5
+
6
+ import pandas as pd
7
+ from pathlib import Path
8
+ from typing import Dict, Any, List, Optional
9
+ from .extractor_base import MetadataExtractor
10
+
11
+
12
+ class CodeGenerator:
13
+ """Python代码生成器"""
14
+
15
+ def __init__(self, data: pd.DataFrame, data_type: str):
16
+ self.data = data
17
+ self.data_type = data_type
18
+
19
+ def generate_enum_code(self,
20
+ class_name: str,
21
+ base_class: str = "EffectEnum",
22
+ meta_class: str = "EffectMeta") -> str:
23
+ """生成枚举类代码"""
24
+
25
+ # 生成头部
26
+ header = f'''"""记录剪映{self.data_type}元数据, 此文件的内容主要由程序生成"""
27
+
28
+ from .effect_meta import {base_class}
29
+ from .effect_meta import {meta_class}
30
+
31
+ class {class_name}({base_class}):
32
+ '''
33
+
34
+ # 生成枚举项
35
+ items = []
36
+ for _, row in self.data.iterrows():
37
+ item_code = self._generate_item_code(row, meta_class)
38
+ if item_code:
39
+ items.append(item_code)
40
+
41
+ return header + "\n".join(items)
42
+
43
+ def _generate_item_code(self, row: pd.Series, meta_class: str) -> str:
44
+ """生成单个枚举项代码"""
45
+ name = self._clean_identifier(row.get('name', row.get('title', '')))
46
+ title = row.get('title', row.get('name', ''))
47
+ is_vip = row.get('is_vip', False)
48
+ resource_id = row.get('resource_id', '')
49
+ effect_id = row.get('effect_id', '')
50
+ md5 = row.get('md5', '')
51
+
52
+ # 根据数据类型生成不同的代码
53
+ if self.data_type == "fonts":
54
+ return self._generate_font_item(name, title, is_vip, resource_id, effect_id, md5, meta_class)
55
+ elif self.data_type == "text_animations":
56
+ duration = row.get('animation_duration', 0.0)
57
+ return self._generate_animation_item(name, title, is_vip, duration, resource_id, effect_id, md5)
58
+ elif self.data_type == "transitions":
59
+ duration = row.get('default_duration', 1.0)
60
+ is_overlap = row.get('is_overlap', False)
61
+ return self._generate_transition_item(name, title, is_vip, resource_id, effect_id, md5, duration, is_overlap)
62
+ elif self.data_type in ["filters", "video_effects"]:
63
+ params = row.get('params', [])
64
+ return self._generate_effect_with_params(name, title, is_vip, resource_id, effect_id, md5, params, meta_class)
65
+ elif self.data_type == "audio_effects":
66
+ params = row.get('params', [])
67
+ return self._generate_audio_effect(name, title, is_vip, resource_id, effect_id, md5, params)
68
+ else:
69
+ # 默认简单特效
70
+ return self._generate_simple_effect(name, title, is_vip, resource_id, effect_id, md5, meta_class)
71
+
72
+ def _generate_font_item(self, name: str, title: str, is_vip: bool,
73
+ resource_id: str, effect_id: str, md5: str, meta_class: str) -> str:
74
+ """生成字体项"""
75
+ padded_name = self._pad_to_length(name, 20)
76
+ return f' {padded_name}= {meta_class}("{title}", {is_vip}, "{resource_id}", "{effect_id}", "{md5}")'
77
+
78
+ def _generate_animation_item(self, name: str, title: str, is_vip: bool, duration: float,
79
+ resource_id: str, effect_id: str, md5: str) -> str:
80
+ """生成动画项"""
81
+ padded_name = self._pad_to_length(name, 12)
82
+ return f' {padded_name}= Animation_meta("{title}", {is_vip}, {duration:.3f}, "{resource_id}", "{effect_id}", "{md5}")'
83
+
84
+ def _generate_transition_item(self, name: str, title: str, is_vip: bool,
85
+ resource_id: str, effect_id: str, md5: str,
86
+ duration: float, is_overlap: bool) -> str:
87
+ """生成转场项"""
88
+ padded_name = self._pad_to_length(name, 12)
89
+ code = f' {padded_name}= Transition_meta("{title}", {is_vip}, "{resource_id}", "{effect_id}", "{md5}", {duration:.6f}, {is_overlap})\n'
90
+ code += f' """默认时长: {duration:.2f}s"""\n'
91
+ return code
92
+
93
+ def _generate_effect_with_params(self, name: str, title: str, is_vip: bool,
94
+ resource_id: str, effect_id: str, md5: str,
95
+ params: List, meta_class: str) -> str:
96
+ """生成带参数的特效项"""
97
+ padded_name = self._pad_to_length(name, 12)
98
+
99
+ # 生成参数字符串
100
+ param_str = ""
101
+ annotation_str = ' """参数:\n'
102
+
103
+ if params:
104
+ param_lines = []
105
+ for param in params:
106
+ if len(param) >= 4:
107
+ param_lines.append(f' Effect_param("{param[0]}", {param[1]:.3f}, {param[2]:.3f}, {param[3]:.3f})')
108
+ annotation_str += f' - {param[0]}: 默认{param[1]:.2f}, {param[2]:.2f} ~ {param[3]:.2f}\n'
109
+
110
+ if param_lines:
111
+ param_str = "[\n" + ",\n".join(param_lines) + "\n ]"
112
+ annotation_str += ' """\n'
113
+ else:
114
+ param_str = "[]"
115
+ annotation_str = ""
116
+ else:
117
+ param_str = "[]"
118
+ annotation_str = ""
119
+
120
+ code = f' {padded_name}= {meta_class}("{title}", {is_vip}, "{resource_id}", "{effect_id}", "{md5}", {param_str})\n'
121
+ if annotation_str and annotation_str != ' """参数:\n """\n':
122
+ code += annotation_str
123
+
124
+ return code
125
+
126
+ def _generate_audio_effect(self, name: str, title: str, is_vip: bool,
127
+ resource_id: str, effect_id: str, md5: str, params: List) -> str:
128
+ """生成音频特效项"""
129
+ padded_name = self._pad_to_length(name, 12)
130
+
131
+ # 生成参数字符串
132
+ param_str = ""
133
+ annotation_str = ' """参数:\n'
134
+
135
+ if params:
136
+ param_lines = []
137
+ for param in params:
138
+ if len(param) >= 4:
139
+ param_lines.append(f' Audio_effect_param("{param[0]}", {param[1]:.3f}, {param[2]:.3f}, {param[3]:.3f})')
140
+ annotation_str += f' - {param[0]}: 默认{param[1]:.2f}, {param[2]:.2f} ~ {param[3]:.2f}\n'
141
+
142
+ if param_lines:
143
+ param_str = "[\n" + ",\n".join(param_lines) + "\n ]"
144
+ annotation_str += ' """\n'
145
+ else:
146
+ param_str = "[]"
147
+ annotation_str = ""
148
+ else:
149
+ param_str = "[]"
150
+ annotation_str = ""
151
+
152
+ code = f' {padded_name}= Audio_effect_meta("{title}", {is_vip}, "{resource_id}", "{effect_id}", "{md5}", {param_str})\n'
153
+ if annotation_str and annotation_str != ' """参数:\n """\n':
154
+ code += annotation_str
155
+
156
+ return code
157
+
158
+ def _generate_simple_effect(self, name: str, title: str, is_vip: bool,
159
+ resource_id: str, effect_id: str, md5: str, meta_class: str) -> str:
160
+ """生成简单特效项"""
161
+ padded_name = self._pad_to_length(name, 12)
162
+ return f' {padded_name}= {meta_class}("{title}", {is_vip}, "{resource_id}", "{effect_id}", "{md5}", [])'
163
+
164
+ @staticmethod
165
+ def _pad_to_length(s: str, length: int, pad_char: str = " ") -> str:
166
+ """字符串填充,考虑中文字符宽度"""
167
+ non_ascii_count = sum(1 for c in s if ord(c) > 127)
168
+ return s.ljust(length - non_ascii_count, pad_char)
169
+
170
+ @staticmethod
171
+ def _clean_identifier(name: str) -> str:
172
+ """清理标识符名称,用于Python变量名"""
173
+ cleaned = name.replace(' ', '_').replace('-', '_').replace('.', '_')
174
+ # 如果以数字开头,添加下划线前缀
175
+ if cleaned and cleaned[0].isdigit():
176
+ cleaned = "_" + cleaned
177
+ return cleaned
178
+
179
+ def save_to_file(self, output_file: str, class_name: str,
180
+ base_class: str = "EffectEnum", meta_class: str = "EffectMeta") -> None:
181
+ """保存代码到文件"""
182
+ code = self.generate_enum_code(class_name, base_class, meta_class)
183
+
184
+ output_path = Path(output_file)
185
+ output_path.parent.mkdir(parents=True, exist_ok=True)
186
+
187
+ with open(output_path, 'w', encoding='utf-8') as f:
188
+ f.write(code)
189
+
190
+ print(f"Generated {len(self.data)} items to {output_file}")
191
+
192
+
193
+ class LegacyCodeGenerator:
194
+ """兼容旧版代码生成格式的生成器"""
195
+
196
+ @staticmethod
197
+ def generate_from_extractor(extractor: MetadataExtractor, output_file: str,
198
+ template_type: str = "effect") -> None:
199
+ """从提取器生成代码(兼容旧版格式)"""
200
+ data = extractor.extract()
201
+
202
+ with open(output_file, 'w', encoding='utf-8') as f:
203
+ for _, row in data.iterrows():
204
+ if template_type == "font":
205
+ LegacyCodeGenerator._write_font_line(f, row)
206
+ elif template_type == "animation":
207
+ LegacyCodeGenerator._write_animation_line(f, row)
208
+ elif template_type == "transition":
209
+ LegacyCodeGenerator._write_transition_line(f, row)
210
+ elif template_type == "effect":
211
+ LegacyCodeGenerator._write_effect_line(f, row)
212
+ elif template_type == "audio_effect":
213
+ LegacyCodeGenerator._write_audio_effect_line(f, row)
214
+
215
+ print(f"Generated {len(data)} items to {output_file}")
216
+
217
+ @staticmethod
218
+ def _write_font_line(f, row):
219
+ name = MetadataExtractor.clean_identifier(row['title'])
220
+ padded_name = MetadataExtractor.pad_to_length(name, 20)
221
+ f.write(f' {padded_name}= Effect_meta("{row["title"]}", {row["is_vip"]}, "{row["resource_id"]}", "{row["effect_id"]}", "{row["md5"]}")\n')
222
+
223
+ @staticmethod
224
+ def _write_animation_line(f, row):
225
+ name = MetadataExtractor.clean_identifier(row['name'])
226
+ padded_name = MetadataExtractor.pad_to_length(name, 12)
227
+ duration = row.get('animation_duration', 0.0)
228
+ f.write(f' {padded_name}= Animation_meta("{row["name"]}", {row["is_vip"]}, {duration:.3f}, "{row["resource_id"]}", "{row["effect_id"]}", "{row["md5"]}")\n')
229
+
230
+ @staticmethod
231
+ def _write_transition_line(f, row):
232
+ name = MetadataExtractor.clean_identifier(row['name'])
233
+ padded_name = MetadataExtractor.pad_to_length(name, 12)
234
+ f.write(f' {padded_name}= Transition_meta("{row["name"]}", {row["is_vip"]}, "{row["resource_id"]}", "{row["effect_id"]}", "{row["md5"]}", {row["default_duration"]:.6f}, {row["is_overlap"]})\n')
235
+ f.write(f' """默认时长: {row["default_duration"]:.2f}s"""\n')
236
+
237
+ @staticmethod
238
+ def _write_effect_line(f, row):
239
+ name = MetadataExtractor.clean_identifier(row.get('title', row.get('name', '')))
240
+ padded_name = MetadataExtractor.pad_to_length(name, 12)
241
+ f.write(f' {padded_name}= Effect_meta("{row.get("title", row.get("name", ""))}", {row["is_vip"]}, "{row["resource_id"]}", "{row["effect_id"]}", "{row["md5"]}", [])\n')
242
+
243
+ @staticmethod
244
+ def _write_audio_effect_line(f, row):
245
+ name = MetadataExtractor.clean_identifier(row['effect_name'])
246
+ padded_name = MetadataExtractor.pad_to_length(name, 12)
247
+ f.write(f' {padded_name}= Audio_effect_meta("{row["effect_name"]}", {row["is_vip"]}, "{row["resource_id"]}", "{row["effect_id"]}", "{row["md5"]}", [])\n')