ik commited on
Commit
12ddc9e
·
verified ·
1 Parent(s): aa51d34

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +88 -0
  2. README.md +3 -9
  3. __pycache__/main_ui.cpython-311.pyc +0 -0
  4. colab_launcher.py +157 -0
  5. installed_tools.json +40 -0
  6. main_ui.py +1556 -0
  7. requirements.txt +12 -0
  8. tools/beatsyncer/__init__.py +1 -0
  9. tools/beatsyncer/__pycache__/__init__.cpython-311.pyc +0 -0
  10. tools/beatsyncer/__pycache__/__init__.cpython-312.pyc +0 -0
  11. tools/beatsyncer/__pycache__/beatsyncer_tool.cpython-311.pyc +0 -0
  12. tools/beatsyncer/__pycache__/beatsyncer_tool.cpython-312.pyc +0 -0
  13. tools/beatsyncer/__pycache__/beatsyncer_ui.cpython-311.pyc +0 -0
  14. tools/beatsyncer/__pycache__/beatsyncer_ui.cpython-312.pyc +0 -0
  15. tools/beatsyncer/beatsyncer_tool.py +470 -0
  16. tools/beatsyncer/beatsyncer_ui.py +151 -0
  17. tools/beatsyncer/clips/.DS_Store +0 -0
  18. tools/beatsyncer/clips/1657591309536.mp4 +3 -0
  19. tools/beatsyncer/clips/1657591680538.mp4 +3 -0
  20. tools/beatsyncer/clips/1657591832727.mp4 +3 -0
  21. tools/beatsyncer/clips/1657616409629.mp4 +3 -0
  22. tools/beatsyncer/clips/1657616591950.mp4 +3 -0
  23. tools/beatsyncer/clips/1657616660848.mp4 +3 -0
  24. tools/beatsyncer/clips/1657616730783.mp4 +3 -0
  25. tools/beatsyncer/clips/1657616788731.mp4 +3 -0
  26. tools/beatsyncer/clips/1657616954951.mp4 +3 -0
  27. tools/beatsyncer/clips/1657923632784.mp4 +3 -0
  28. tools/beatsyncer/clips/288298605_1687187951639494_579221224540621563_n.mp4 +3 -0
  29. tools/beatsyncer/clips/289438954_5129286887125053_220682033208031067_n.mp4 +3 -0
  30. tools/beatsyncer/clips/289568281_1462345327513048_3022742540007505592_n.mp4 +3 -0
  31. tools/beatsyncer/clips/290154393_565236208447925_526246679178540543_n.mp4 +3 -0
  32. tools/beatsyncer/clips/290702607_425330016154178_254797435320646581_n.mp4 +3 -0
  33. tools/beatsyncer/clips/290990526_409256130973654_5788653198617875871_n.mp4 +3 -0
  34. tools/beatsyncer/clips/291032176_120067890734229_1814549935092808193_n.mp4 +3 -0
  35. tools/beatsyncer/clips/657591201007.mp4 +3 -0
  36. tools/beatsyncer/clips/657591253340.mp4 +3 -0
  37. tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_001.mp4 +3 -0
  38. tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_002.mp4 +3 -0
  39. tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_003.mp4 +3 -0
  40. tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_004.mp4 +3 -0
  41. tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_005.mp4 +3 -0
  42. tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_006.mp4 +3 -0
  43. tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_007.mp4 +3 -0
  44. tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_008.mp4 +3 -0
  45. tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_009.mp4 +3 -0
  46. tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_010.mp4 +3 -0
  47. tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_001.mp4 +3 -0
  48. tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_002.mp4 +3 -0
  49. tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_003.mp4 +3 -0
  50. tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_004.mp4 +3 -0
.gitattributes CHANGED
@@ -33,3 +33,91 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ tools/beatsyncer/clips/1657591309536.mp4 filter=lfs diff=lfs merge=lfs -text
37
+ tools/beatsyncer/clips/1657591680538.mp4 filter=lfs diff=lfs merge=lfs -text
38
+ tools/beatsyncer/clips/1657591832727.mp4 filter=lfs diff=lfs merge=lfs -text
39
+ tools/beatsyncer/clips/1657616409629.mp4 filter=lfs diff=lfs merge=lfs -text
40
+ tools/beatsyncer/clips/1657616591950.mp4 filter=lfs diff=lfs merge=lfs -text
41
+ tools/beatsyncer/clips/1657616660848.mp4 filter=lfs diff=lfs merge=lfs -text
42
+ tools/beatsyncer/clips/1657616730783.mp4 filter=lfs diff=lfs merge=lfs -text
43
+ tools/beatsyncer/clips/1657616788731.mp4 filter=lfs diff=lfs merge=lfs -text
44
+ tools/beatsyncer/clips/1657616954951.mp4 filter=lfs diff=lfs merge=lfs -text
45
+ tools/beatsyncer/clips/1657923632784.mp4 filter=lfs diff=lfs merge=lfs -text
46
+ tools/beatsyncer/clips/288298605_1687187951639494_579221224540621563_n.mp4 filter=lfs diff=lfs merge=lfs -text
47
+ tools/beatsyncer/clips/289438954_5129286887125053_220682033208031067_n.mp4 filter=lfs diff=lfs merge=lfs -text
48
+ tools/beatsyncer/clips/289568281_1462345327513048_3022742540007505592_n.mp4 filter=lfs diff=lfs merge=lfs -text
49
+ tools/beatsyncer/clips/290154393_565236208447925_526246679178540543_n.mp4 filter=lfs diff=lfs merge=lfs -text
50
+ tools/beatsyncer/clips/290702607_425330016154178_254797435320646581_n.mp4 filter=lfs diff=lfs merge=lfs -text
51
+ tools/beatsyncer/clips/290990526_409256130973654_5788653198617875871_n.mp4 filter=lfs diff=lfs merge=lfs -text
52
+ tools/beatsyncer/clips/291032176_120067890734229_1814549935092808193_n.mp4 filter=lfs diff=lfs merge=lfs -text
53
+ tools/beatsyncer/clips/657591201007.mp4 filter=lfs diff=lfs merge=lfs -text
54
+ tools/beatsyncer/clips/657591253340.mp4 filter=lfs diff=lfs merge=lfs -text
55
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_001.mp4 filter=lfs diff=lfs merge=lfs -text
56
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_002.mp4 filter=lfs diff=lfs merge=lfs -text
57
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_003.mp4 filter=lfs diff=lfs merge=lfs -text
58
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_004.mp4 filter=lfs diff=lfs merge=lfs -text
59
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_005.mp4 filter=lfs diff=lfs merge=lfs -text
60
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_006.mp4 filter=lfs diff=lfs merge=lfs -text
61
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_007.mp4 filter=lfs diff=lfs merge=lfs -text
62
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_008.mp4 filter=lfs diff=lfs merge=lfs -text
63
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_009.mp4 filter=lfs diff=lfs merge=lfs -text
64
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_010.mp4 filter=lfs diff=lfs merge=lfs -text
65
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_001.mp4 filter=lfs diff=lfs merge=lfs -text
66
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_002.mp4 filter=lfs diff=lfs merge=lfs -text
67
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_003.mp4 filter=lfs diff=lfs merge=lfs -text
68
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_004.mp4 filter=lfs diff=lfs merge=lfs -text
69
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_005.mp4 filter=lfs diff=lfs merge=lfs -text
70
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_006.mp4 filter=lfs diff=lfs merge=lfs -text
71
+ tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_007.mp4 filter=lfs diff=lfs merge=lfs -text
72
+ tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_001.mp4 filter=lfs diff=lfs merge=lfs -text
73
+ tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_002.mp4 filter=lfs diff=lfs merge=lfs -text
74
+ tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_003.mp4 filter=lfs diff=lfs merge=lfs -text
75
+ tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_004.mp4 filter=lfs diff=lfs merge=lfs -text
76
+ tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_005.mp4 filter=lfs diff=lfs merge=lfs -text
77
+ tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_006.mp4 filter=lfs diff=lfs merge=lfs -text
78
+ tools/beatsyncer/clips/Snapinsta.app_video_6F4E14515715C2DB44E4959237066298_video_dashinit_scene_001.mp4 filter=lfs diff=lfs merge=lfs -text
79
+ tools/beatsyncer/clips/Snapinsta.app_video_6F4E14515715C2DB44E4959237066298_video_dashinit_scene_002.mp4 filter=lfs diff=lfs merge=lfs -text
80
+ tools/beatsyncer/clips/Snapinsta.app_video_7944DC0BB36028CA2D36023098F3029C_video_dashinit.mp4 filter=lfs diff=lfs merge=lfs -text
81
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_001.mp4 filter=lfs diff=lfs merge=lfs -text
82
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_002.mp4 filter=lfs diff=lfs merge=lfs -text
83
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_003.mp4 filter=lfs diff=lfs merge=lfs -text
84
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_004.mp4 filter=lfs diff=lfs merge=lfs -text
85
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_005.mp4 filter=lfs diff=lfs merge=lfs -text
86
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_006.mp4 filter=lfs diff=lfs merge=lfs -text
87
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_007.mp4 filter=lfs diff=lfs merge=lfs -text
88
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_008.mp4 filter=lfs diff=lfs merge=lfs -text
89
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_009.mp4 filter=lfs diff=lfs merge=lfs -text
90
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_010.mp4 filter=lfs diff=lfs merge=lfs -text
91
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_011.mp4 filter=lfs diff=lfs merge=lfs -text
92
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_012.mp4 filter=lfs diff=lfs merge=lfs -text
93
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_013.mp4 filter=lfs diff=lfs merge=lfs -text
94
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_014.mp4 filter=lfs diff=lfs merge=lfs -text
95
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_015.mp4 filter=lfs diff=lfs merge=lfs -text
96
+ tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_016.mp4 filter=lfs diff=lfs merge=lfs -text
97
+ tools/beatsyncer/clips/ai-generated-8079926_1280.png filter=lfs diff=lfs merge=lfs -text
98
+ tools/beatsyncer/clips/ai-generated-8601539_1280.jpg filter=lfs diff=lfs merge=lfs -text
99
+ tools/beatsyncer/clips/ai-generated-9151678_1280.jpg filter=lfs diff=lfs merge=lfs -text
100
+ tools/beatsyncer/clips/antique-car-7577214_1280.jpg filter=lfs diff=lfs merge=lfs -text
101
+ tools/beatsyncer/clips/automobile-1850862_1280.jpg filter=lfs diff=lfs merge=lfs -text
102
+ tools/beatsyncer/clips/bmw-6236208_1280.jpg filter=lfs diff=lfs merge=lfs -text
103
+ tools/beatsyncer/clips/car-171415_1280.jpg filter=lfs diff=lfs merge=lfs -text
104
+ tools/beatsyncer/clips/car-219813_1280.jpg filter=lfs diff=lfs merge=lfs -text
105
+ tools/beatsyncer/clips/car-5548243_1280.jpg filter=lfs diff=lfs merge=lfs -text
106
+ tools/beatsyncer/clips/car-5548244_1280.jpg filter=lfs diff=lfs merge=lfs -text
107
+ tools/beatsyncer/clips/car-6306695_1280.jpg filter=lfs diff=lfs merge=lfs -text
108
+ tools/beatsyncer/clips/car-6483720_1280.jpg filter=lfs diff=lfs merge=lfs -text
109
+ tools/beatsyncer/clips/car-6589240_1280.jpg filter=lfs diff=lfs merge=lfs -text
110
+ tools/beatsyncer/clips/car-6658749_1280.jpg filter=lfs diff=lfs merge=lfs -text
111
+ tools/beatsyncer/clips/car-7139944_1280.jpg filter=lfs diff=lfs merge=lfs -text
112
+ tools/beatsyncer/clips/car-7203265_1280(1).jpg filter=lfs diff=lfs merge=lfs -text
113
+ tools/beatsyncer/clips/car-7203265_1280.jpg filter=lfs diff=lfs merge=lfs -text
114
+ tools/beatsyncer/clips/car-7725834_1280.jpg filter=lfs diff=lfs merge=lfs -text
115
+ tools/beatsyncer/clips/car-7830737_1280.jpg filter=lfs diff=lfs merge=lfs -text
116
+ tools/beatsyncer/clips/car-8141820_1280.jpg filter=lfs diff=lfs merge=lfs -text
117
+ tools/beatsyncer/clips/fashion-1399346_1280.jpg filter=lfs diff=lfs merge=lfs -text
118
+ tools/beatsyncer/clips/mercedes-benz-7383931_1280.jpg filter=lfs diff=lfs merge=lfs -text
119
+ tools/beatsyncer/clips/speedy-car-8391554_1280.jpg filter=lfs diff=lfs merge=lfs -text
120
+ tools/beatsyncer/clips/sport-4155825_1280.jpg filter=lfs diff=lfs merge=lfs -text
121
+ tools/beatsyncer/clips/vintage-car-7300881_1280.jpg filter=lfs diff=lfs merge=lfs -text
122
+ tools/beatsyncer/clips/vintage-rolls-royce-7414656_1280.jpg filter=lfs diff=lfs merge=lfs -text
123
+ tools/beatsyncer/tmp/input_audio.mp3 filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,12 +1,6 @@
1
  ---
2
- title: AI Tools Platform
3
- emoji: 💻
4
- colorFrom: blue
5
- colorTo: pink
6
  sdk: gradio
7
- sdk_version: 5.48.0
8
- app_file: app.py
9
- pinned: false
10
  ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: AI_Tools_Platform
3
+ app_file: main_ui.py
 
 
4
  sdk: gradio
5
+ sdk_version: 4.19.2
 
 
6
  ---
 
 
__pycache__/main_ui.cpython-311.pyc ADDED
Binary file (64.7 kB). View file
 
colab_launcher.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import json
4
+ import importlib.util
5
+ from pathlib import Path
6
+
7
+ # Define paths
8
+ DRIVE_ROOT = "/content/drive/MyDrive/AI_Tools_Platform"
9
+ TOOLS_DIR = os.path.join(DRIVE_ROOT, "tools")
10
+ INSTALLED_TOOLS_JSON = os.path.join(DRIVE_ROOT, "installed_tools.json")
11
+
12
+ # Import the Tool class from main_ui
13
+ from main_ui import Tool, MainUI
14
+
15
+ def setup_environment():
16
+ print("Setting up environment...")
17
+
18
+ # Create tools directory in Google Drive if it doesn't exist
19
+ os.makedirs(TOOLS_DIR, exist_ok=True)
20
+ print(f"Tools directory in Drive: {TOOLS_DIR}")
21
+
22
+ # Create installed_tools.json in Google Drive if it doesn't exist
23
+ if not os.path.exists(INSTALLED_TOOLS_JSON):
24
+ with open(INSTALLED_TOOLS_JSON, 'w') as f:
25
+ json.dump({"tools": []}, f, indent=4)
26
+ print(f"Created initial installed_tools.json in Drive")
27
+ else:
28
+ print(f"Using existing installed_tools.json from Drive")
29
+
30
+ # Verify the content of installed_tools.json
31
+ try:
32
+ with open(INSTALLED_TOOLS_JSON, 'r') as f:
33
+ tools_data = json.load(f)
34
+ if not isinstance(tools_data, dict) or "tools" not in tools_data:
35
+ print("Warning: installed_tools.json has incorrect format. Fixing...")
36
+ with open(INSTALLED_TOOLS_JSON, 'w') as f:
37
+ json.dump({"tools": []}, f, indent=4)
38
+ except (json.JSONDecodeError, UnicodeDecodeError):
39
+ print("Error: installed_tools.json is not valid JSON. Fixing...")
40
+ with open(INSTALLED_TOOLS_JSON, 'w') as f:
41
+ json.dump({"tools": []}, f, indent=4)
42
+
43
+ # Add tools directory to Python path
44
+ if TOOLS_DIR not in sys.path:
45
+ sys.path.append(TOOLS_DIR)
46
+ if os.getcwd() not in sys.path:
47
+ sys.path.append(os.getcwd())
48
+
49
+ print("\nEnvironment set up successfully!")
50
+
51
+ # Override MainUI methods to use direct file paths
52
+ class ColabMainUI(MainUI):
53
+ def __init__(self):
54
+ # Set up paths before calling parent's __init__
55
+ self.tools = {} # Initialize tools dictionary
56
+ self.tools_dir = TOOLS_DIR
57
+ self.installed_tools_json = INSTALLED_TOOLS_JSON
58
+ self.marketplace_url = "https://marketplace.vidplus.app/api/marketplace"
59
+ self.version = "0.1"
60
+
61
+ # Create tools directory if it doesn't exist
62
+ os.makedirs('tools', exist_ok=True)
63
+
64
+ # Load tools manually instead of calling parent's __init__
65
+ self.load_installed_tools()
66
+
67
+ def load_installed_tools(self):
68
+ """Load installed tools directly from Google Drive"""
69
+ print(f"Loading tools from {self.installed_tools_json}")
70
+ try:
71
+ if os.path.exists(self.installed_tools_json):
72
+ with open(self.installed_tools_json, 'r') as f:
73
+ data = json.load(f)
74
+
75
+ for tool_data in data.get('tools', []):
76
+ # If ui_module is specified, dynamically import it
77
+ if tool_data.get('ui_module'):
78
+ try:
79
+ # Modify the module path to use the direct path
80
+ module_name = tool_data['ui_module']
81
+ if module_name.startswith('tools.'):
82
+ # Extract the relative path and use the full path
83
+ rel_path = module_name.replace('tools.', '')
84
+ module_path = os.path.join(TOOLS_DIR, rel_path.replace('.', '/') + '.py')
85
+ print(f"Loading module from: {module_path}")
86
+
87
+ # Use importlib to import the module
88
+ spec = importlib.util.spec_from_file_location(module_name, module_path)
89
+ if spec:
90
+ module = importlib.util.module_from_spec(spec)
91
+ spec.loader.exec_module(module)
92
+ ui_class = getattr(module, tool_data['ui_class'])
93
+
94
+ self.tools[tool_data['id']] = Tool(
95
+ name=tool_data['name'],
96
+ description=tool_data['description'],
97
+ ui_class=ui_class,
98
+ icon=tool_data['icon'],
99
+ version=tool_data['version'],
100
+ ui_module=tool_data['ui_module']
101
+ )
102
+ else:
103
+ print(f"Could not find module: {module_path}")
104
+ else:
105
+ # Standard import for non-tools modules
106
+ module = importlib.import_module(module_name)
107
+ ui_class = getattr(module, tool_data['ui_class'])
108
+
109
+ self.tools[tool_data['id']] = Tool(
110
+ name=tool_data['name'],
111
+ description=tool_data['description'],
112
+ ui_class=ui_class,
113
+ icon=tool_data['icon'],
114
+ version=tool_data['version'],
115
+ ui_module=tool_data['ui_module']
116
+ )
117
+ except (ImportError, AttributeError) as e:
118
+ print(f"Error loading tool {tool_data['name']}: {e}")
119
+ import traceback
120
+ traceback.print_exc()
121
+ except Exception as e:
122
+ print(f"Error loading installed tools: {e}")
123
+ import traceback
124
+ traceback.print_exc()
125
+
126
+ def save_installed_tools(self):
127
+ """Save installed tools directly to Google Drive"""
128
+ data = {
129
+ "tools": [
130
+ {
131
+ "id": tool_id,
132
+ "name": tool.name,
133
+ "description": tool.description,
134
+ "icon": tool.icon,
135
+ "version": tool.version,
136
+ "ui_class": tool.ui_class.__name__,
137
+ "ui_module": tool.ui_module
138
+ }
139
+ for tool_id, tool in self.tools.items()
140
+ ]
141
+ }
142
+
143
+ with open(self.installed_tools_json, 'w') as f:
144
+ json.dump(data, f, indent=4)
145
+ print(f"Saved tools configuration to {self.installed_tools_json}")
146
+
147
+ if __name__ == "__main__":
148
+ setup_environment()
149
+
150
+ # Import and run the modified UI
151
+ print("\nStarting AI Tools Platform...")
152
+
153
+ ui = ColabMainUI()
154
+ demo = ui.create_ui()
155
+ demo.queue()
156
+ demo.title = "AI Tools Platform"
157
+ demo.launch(debug=True, share=True) # share=True to get a public URL
installed_tools.json ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tools": [
3
+ {
4
+ "id": "image_background_remover",
5
+ "name": "Image Background Remover",
6
+ "description": "A tool that removes backgrounds from images",
7
+ "icon": "\ud83d\uddbc\ufe0f",
8
+ "version": "1.0.0",
9
+ "ui_class": "ImageBackgroundRemoverUI",
10
+ "ui_module": "tools.image_background_remover.image_background_remover_ui"
11
+ },
12
+ {
13
+ "id": "beatsyncer",
14
+ "name": "Beatsyncer",
15
+ "description": "Create amazing videos synchronized with music beats",
16
+ "icon": "\ud83c\udfb5",
17
+ "version": "1.0.0",
18
+ "ui_class": "BeatsyncerUI",
19
+ "ui_module": "tools.beatsyncer.beatsyncer_ui"
20
+ },
21
+ {
22
+ "id": "image_background_remover",
23
+ "name": "Image Background Remover",
24
+ "description": "A tool that removes backgrounds from images",
25
+ "icon": "\ud83d\uddbc\ufe0f",
26
+ "version": "1.0.0",
27
+ "ui_class": "ImageBackgroundRemoverUI",
28
+ "ui_module": "tools.image_background_remover.image_background_remover_ui"
29
+ },
30
+ {
31
+ "id": "image_background_remover",
32
+ "name": "Image Background Remover",
33
+ "description": "A tool that removes backgrounds from images",
34
+ "icon": "\ud83d\uddbc\ufe0f",
35
+ "version": "1.0.0",
36
+ "ui_class": "ImageBackgroundRemoverUI",
37
+ "ui_module": "tools.image_background_remover.image_background_remover_ui"
38
+ }
39
+ ]
40
+ }
main_ui.py ADDED
@@ -0,0 +1,1556 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import importlib
4
+ import requests
5
+ import gradio as gr
6
+ import subprocess
7
+
8
+ class Tool:
9
+ def __init__(self, name, description, ui_class, icon="🔧", version="0.1", ui_module=None):
10
+ self.name = name
11
+ self.description = description
12
+ self.ui_class = ui_class
13
+ self.icon = icon
14
+ self.version = version
15
+ self.ui_module = ui_module
16
+
17
+ class MainUI:
18
+ def __init__(self):
19
+ # Load installed tools from JSON
20
+ self.tools = {}
21
+ self.load_installed_tools()
22
+
23
+ # Marketplace server URL
24
+ self.marketplace_url = "https://marketplace.vidplus.app/api/marketplace"
25
+ self.version = "0.1"
26
+
27
+ # Create tools directory if it doesn't exist
28
+ os.makedirs('tools', exist_ok=True)
29
+
30
+ def load_installed_tools(self):
31
+ """Load installed tools from JSON file"""
32
+ try:
33
+ if os.path.exists('installed_tools.json'):
34
+ with open('installed_tools.json', 'r') as f:
35
+ data = json.load(f)
36
+
37
+ for tool_data in data.get('tools', []):
38
+ # If ui_module is specified, dynamically import it
39
+ if tool_data.get('ui_module'):
40
+ try:
41
+ module = importlib.import_module(tool_data['ui_module'])
42
+ ui_class = getattr(module, tool_data['ui_class'])
43
+
44
+ self.tools[tool_data['id']] = Tool(
45
+ name=tool_data['name'],
46
+ description=tool_data['description'],
47
+ ui_class=ui_class,
48
+ icon=tool_data['icon'],
49
+ version=tool_data['version'],
50
+ ui_module=tool_data['ui_module']
51
+ )
52
+ except (ImportError, AttributeError) as e:
53
+ print(f"Error loading tool {tool_data['name']}: {e}")
54
+ except Exception as e:
55
+ print(f"Error loading installed tools: {e}")
56
+ # Fallback to default BeatSyncer tool
57
+ # self.tools = {
58
+ # "beatsyncer": Tool(
59
+ # name="BeatSyncer",
60
+ # description="Create amazing videos synchronized with music beats",
61
+ # ui_class=BeatSyncerUI,
62
+ # icon="🎵"
63
+ # )
64
+ # }
65
+
66
+ def save_installed_tools(self):
67
+ """Save installed tools to JSON file"""
68
+ data = {
69
+ "tools": [
70
+ {
71
+ "id": tool_id,
72
+ "name": tool.name,
73
+ "description": tool.description,
74
+ "icon": tool.icon,
75
+ "version": tool.version,
76
+ "ui_class": tool.ui_class.__name__,
77
+ "ui_module": tool.ui_module
78
+ }
79
+ for tool_id, tool in self.tools.items()
80
+ ]
81
+ }
82
+
83
+ with open('installed_tools.json', 'w') as f:
84
+ json.dump(data, f, indent=4)
85
+
86
+ def fetch_marketplace_tools(self):
87
+ """Fetch available tools from marketplace server"""
88
+ try:
89
+ response = requests.get(f"{self.marketplace_url}/tools")
90
+ if response.status_code == 200:
91
+ # Get the list of tools from the marketplace
92
+ marketplace_tools = response.json()
93
+
94
+ # Ensure we're working with the latest state of installed tools
95
+ # This is important after a tool has been removed
96
+ if os.path.exists('installed_tools.json'):
97
+ with open('installed_tools.json', 'r') as f:
98
+ data = json.load(f)
99
+ installed_tool_ids = [tool['id'] for tool in data.get('tools', [])]
100
+ else:
101
+ installed_tool_ids = []
102
+
103
+ # Update the is_installed flag for each tool
104
+ for tool in marketplace_tools:
105
+ tool['is_installed'] = tool['id'] in installed_tool_ids
106
+
107
+ return marketplace_tools
108
+ else:
109
+ return []
110
+ except Exception as e:
111
+ print(f"Error fetching marketplace tools: {e}")
112
+ return []
113
+
114
+ def fetch_tool_details(self, tool_id):
115
+ """Fetch details for a specific tool from marketplace server"""
116
+ try:
117
+ response = requests.get(f"{self.marketplace_url}/tools/{tool_id}")
118
+ if response.status_code == 200:
119
+ return response.json()
120
+ else:
121
+ return None
122
+ except Exception as e:
123
+ print(f"Error fetching tool details: {e}")
124
+ return None
125
+
126
+ def install_tool(self, tool_id):
127
+ """Install a tool from the marketplace"""
128
+ try:
129
+ # Import required modules
130
+ import importlib
131
+ import importlib.util
132
+ import sys
133
+ import subprocess
134
+ import gradio as gr
135
+
136
+ # Fetch tool data
137
+ response = requests.get(f"{self.marketplace_url}/tools/{tool_id}/download")
138
+ if response.status_code != 200:
139
+ return f"Error: Failed to download tool (Status code: {response.status_code})"
140
+
141
+ tool_data = response.json()
142
+ tool_info = tool_data['tool']
143
+ tool_files = tool_data['files']
144
+
145
+ # Check and install dependencies if they exist
146
+ dependencies = tool_info.get('dependencies', [])
147
+ if dependencies:
148
+ try:
149
+ # Read current requirements.txt
150
+ current_requirements = []
151
+ if os.path.exists('requirements.txt'):
152
+ with open('requirements.txt', 'r') as f:
153
+ current_requirements = [line.strip() for line in f.readlines() if line.strip()]
154
+
155
+ # Check which dependencies need to be installed
156
+ to_install = []
157
+ to_add_to_requirements = []
158
+
159
+ for dep in dependencies:
160
+ # Extract just the package name (ignore version requirements)
161
+ pkg_name = dep.split('>=')[0].split('==')[0].split('>')[0].split('<')[0].strip()
162
+
163
+ # Check if dependency is already in requirements.txt
164
+ is_in_requirements = False
165
+ for req in current_requirements:
166
+ req_name = req.split('>=')[0].split('==')[0].split('>')[0].split('<')[0].strip()
167
+ if req_name == pkg_name:
168
+ is_in_requirements = True
169
+ break
170
+
171
+ if not is_in_requirements:
172
+ to_add_to_requirements.append(dep)
173
+
174
+ try:
175
+ # Try to import the package to check if it's installed
176
+ importlib.import_module(pkg_name)
177
+ except ImportError:
178
+ # Package not installed, add to installation list
179
+ to_install.append(dep)
180
+
181
+ # Install missing dependencies
182
+ if to_install:
183
+ for dep in to_install:
184
+ # Use subprocess to run pip install
185
+ subprocess.check_call([sys.executable, "-m", "pip", "install", dep])
186
+
187
+ # Append new dependencies to requirements.txt
188
+ if to_add_to_requirements:
189
+ with open('requirements.txt', 'a') as f:
190
+ for dep in to_add_to_requirements:
191
+ f.write(f"\n{dep}")
192
+ print(f"Added {len(to_add_to_requirements)} dependencies to requirements.txt")
193
+
194
+ except Exception as e:
195
+ return f"Error installing dependencies: {e}"
196
+
197
+ # Create tool directory
198
+ tool_dir = os.path.join('tools', tool_id)
199
+ os.makedirs(tool_dir, exist_ok=True)
200
+
201
+ # Write tool files
202
+ for file_name, file_content in tool_files.items():
203
+ # Rename files to match our convention
204
+ target_file_name = file_name
205
+ if file_name.endswith('_tool_logic.py'):
206
+ target_file_name = f"{tool_id}_tool.py"
207
+ elif file_name.endswith('_tool_ui.py'):
208
+ target_file_name = f"{tool_id}_ui.py"
209
+
210
+ with open(os.path.join(tool_dir, target_file_name), 'w') as f:
211
+ f.write(file_content)
212
+
213
+ # Create __init__.py to make the directory a package
214
+ with open(os.path.join(tool_dir, '__init__.py'), 'w') as f:
215
+ f.write(f"# {tool_info['name']} package\n")
216
+
217
+ # Add tool to installed tools
218
+ ui_module = f"tools.{tool_id}.{tool_id}_ui"
219
+ ui_class = f"{tool_id.title().replace('_', '')}UI"
220
+
221
+ # Update installed_tools.json
222
+ with open('installed_tools.json', 'r') as f:
223
+ data = json.load(f)
224
+
225
+ data['tools'].append({
226
+ "id": tool_id,
227
+ "name": tool_info['name'],
228
+ "description": tool_info['description'],
229
+ "icon": tool_info['icon'],
230
+ "version": tool_info['version'],
231
+ "ui_class": ui_class,
232
+ "ui_module": ui_module
233
+ })
234
+
235
+ with open('installed_tools.json', 'w') as f:
236
+ json.dump(data, f, indent=4)
237
+
238
+ # Dynamically load the newly installed tool
239
+ try:
240
+ # Reload the module if it was already imported
241
+ if ui_module in sys.modules:
242
+ importlib.reload(sys.modules[ui_module])
243
+
244
+ # Import the module
245
+ module = importlib.import_module(ui_module)
246
+ ui_class_obj = getattr(module, ui_class)
247
+
248
+ # Add the tool to self.tools
249
+ self.tools[tool_id] = Tool(
250
+ name=tool_info['name'],
251
+ description=tool_info['description'],
252
+ ui_class=ui_class_obj,
253
+ icon=tool_info['icon'],
254
+ version=tool_info['version'],
255
+ ui_module=ui_module
256
+ )
257
+
258
+ # Flag to indicate this is a newly installed tool that needs special handling
259
+ self.tools[tool_id].newly_installed = True
260
+
261
+ # Return success message
262
+ return f"Successfully installed {tool_info['name']}. The tool is now available in the Tools page."
263
+ except Exception as e:
264
+ print(f"Error dynamically loading tool: {e}")
265
+ import traceback
266
+ traceback.print_exc()
267
+ # Still return success but with a restart message
268
+ return f"Successfully installed {tool_info['name']}. Please restart the application to use the new tool."
269
+ except Exception as e:
270
+ return f"Error installing tool: {e}"
271
+
272
+ def remove_tool(self, tool_id):
273
+ """Remove an installed tool"""
274
+ try:
275
+ if tool_id not in self.tools:
276
+ return "Error: Tool is not installed"
277
+
278
+ # Get tool info before removal
279
+ tool_name = self.tools[tool_id].name
280
+
281
+ # Remove tool directory
282
+ tool_dir = os.path.join('tools', tool_id)
283
+ if os.path.exists(tool_dir):
284
+ import shutil
285
+ shutil.rmtree(tool_dir)
286
+ print(f"Removed tool directory: {tool_dir}")
287
+
288
+ # Remove tool from installed_tools.json
289
+ if os.path.exists('installed_tools.json'):
290
+ with open('installed_tools.json', 'r') as f:
291
+ data = json.load(f)
292
+
293
+ # Filter out the tool to be removed
294
+ data['tools'] = [tool for tool in data['tools'] if tool['id'] != tool_id]
295
+
296
+ # Save updated tools list
297
+ with open('installed_tools.json', 'w') as f:
298
+ json.dump(data, f, indent=4)
299
+
300
+ print(f"Removed tool from installed_tools.json: {tool_id}")
301
+
302
+ # Remove tool from self.tools
303
+ del self.tools[tool_id]
304
+
305
+ return f"✅ Successfully removed {tool_name}"
306
+ except Exception as e:
307
+ print(f"Error removing tool: {e}")
308
+ import traceback
309
+ traceback.print_exc()
310
+ return f"Error removing tool: {str(e)}"
311
+
312
+ def create_tool_card(self, name):
313
+ return f"""
314
+ <div style="
315
+ background: #E6E6E6;
316
+ border-radius: 16px;
317
+ width: 200px;
318
+ height: 200px;
319
+ display: flex;
320
+ justify-content: center;
321
+ align-items: center;
322
+ cursor: pointer;
323
+ transition: transform 0.2s;
324
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
325
+ &:hover {{
326
+ transform: scale(1.02);
327
+ }}">
328
+ <span style="color: #1a1a1a; font-size: 16px; font-weight: 500;">{name}</span>
329
+ </div>
330
+ """
331
+
332
+ def create_ui(self):
333
+ css = """
334
+ :root {
335
+ --sidebar-width: 200px;
336
+ }
337
+
338
+ body {
339
+ margin: 0;
340
+ padding: 0;
341
+ overflow: hidden;
342
+ }
343
+
344
+ #sidebar {
345
+ background-color: #0A192F;
346
+ color: white;
347
+ padding: 32px 24px;
348
+ height: 100vh;
349
+ width: var(--sidebar-width);
350
+ position: fixed;
351
+ left: 0;
352
+ top: 0;
353
+ display: flex;
354
+ flex-direction: column;
355
+ box-sizing: border-box;
356
+ z-index: 1000;
357
+ }
358
+
359
+ #main-content {
360
+ background-color: #0F172A;
361
+ min-height: 100vh;
362
+ margin-left: var(--sidebar-width);
363
+ padding: 50px;
364
+ box-sizing: border-box;
365
+ overflow-y: auto;
366
+ position: relative;
367
+ max-height: 100vh; /* Ensure content is scrollable */
368
+ }
369
+
370
+ .nav-button {
371
+ background: none !important;
372
+ border: none !important;
373
+ box-shadow: none !important;
374
+ color: #94A3B8 !important;
375
+ padding: 8px 0 !important;
376
+ margin: 4px 0 !important;
377
+ min-width: unset !important;
378
+ width: 100% !important;
379
+ text-align: left !important;
380
+ font-size: 16px !important;
381
+ font-weight: 500 !important;
382
+ height: auto !important;
383
+ line-height: 1.2 !important;
384
+ }
385
+
386
+ .nav-button:hover {
387
+ color: white !important;
388
+ }
389
+
390
+ .nav-button[variant="primary"] {
391
+ color: white !important;
392
+ }
393
+
394
+ .tool-grid {
395
+ display: grid;
396
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
397
+ gap: 20px;
398
+ padding: 24px 0;
399
+ width: 100%;
400
+ }
401
+
402
+ .version-text {
403
+ margin-top: auto;
404
+ color: #64748B;
405
+ font-size: 12px;
406
+ padding-top: 16px;
407
+ }
408
+
409
+ .title {
410
+ font-size: 32px;
411
+ font-weight: 800;
412
+ margin-bottom: 32px;
413
+ color: white;
414
+ letter-spacing: -0.5px;
415
+ }
416
+
417
+ .page-title {
418
+ font-size: 28px;
419
+ font-weight: 700;
420
+ color: white;
421
+ margin: 0;
422
+ padding: 0;
423
+ letter-spacing: -0.5px;
424
+ }
425
+
426
+ #content-wrapper {
427
+ width: 100%;
428
+ max-width: 100%;
429
+ padding: 24px;
430
+ }
431
+
432
+ .tool-card {
433
+ background: #1E293B !important;
434
+ border: none !important;
435
+ border-radius: 16px !important;
436
+ overflow: hidden !important;
437
+ transition: transform 0.2s !important;
438
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important;
439
+ height: 100% !important;
440
+ display: flex !important;
441
+ flex-direction: column !important;
442
+ width: auto !important;
443
+ min-height: 200px !important;
444
+ justify-content: flex-start !important;
445
+ align-items: stretch !important;
446
+ padding: 0 !important;
447
+ color: white !important;
448
+ text-align: left !important;
449
+ cursor: pointer !important;
450
+ }
451
+
452
+ .tool-card:hover {
453
+ transform: scale(1.02) !important;
454
+ }
455
+
456
+ .tool-card-img {
457
+ width: 100%;
458
+ height: 150px;
459
+ background: #2D3748;
460
+ display: flex;
461
+ align-items: center;
462
+ justify-content: center;
463
+ font-size: 48px;
464
+ color: #38BDF8;
465
+ }
466
+
467
+ .tool-card-content {
468
+ padding: 16px !important;
469
+ flex-grow: 1 !important;
470
+ display: flex !important;
471
+ flex-direction: column !important;
472
+ }
473
+
474
+ .tool-card-title {
475
+ font-size: 18px !important;
476
+ font-weight: 600 !important;
477
+ color: white !important;
478
+ margin: 0 0 8px 0 !important;
479
+ }
480
+
481
+ .tool-card-description {
482
+ font-size: 14px !important;
483
+ color: #94A3B8 !important;
484
+ margin: 0 !important;
485
+ flex-grow: 1 !important;
486
+ }
487
+
488
+ .installed-indicator {
489
+ background-color: #10B981 !important;
490
+ color: white !important;
491
+ font-size: 12px !important;
492
+ font-weight: 600 !important;
493
+ padding: 4px 8px !important;
494
+ border-radius: 4px !important;
495
+ margin-top: 8px !important;
496
+ display: inline-block !important;
497
+ }
498
+
499
+ .tool-card.installed {
500
+ border: 2px solid #10B981 !important;
501
+ }
502
+
503
+ .add-tool-card {
504
+ background: rgba(255, 255, 255, 0.05) !important;
505
+ border: 2px dashed rgba(255, 255, 255, 0.2) !important;
506
+ color: rgba(255, 255, 255, 0.8) !important;
507
+ display: flex !important;
508
+ flex-direction: column !important;
509
+ justify-content: center !important;
510
+ align-items: center !important;
511
+ text-align: center !important;
512
+ padding: 16px !important;
513
+ }
514
+
515
+ .add-tool-icon {
516
+ font-size: 32px;
517
+ margin-bottom: 16px;
518
+ color: rgba(255, 255, 255, 0.5);
519
+ }
520
+
521
+ .tool-card-button {
522
+ background: transparent !important;
523
+ border: none !important;
524
+ padding: 0 !important;
525
+ margin: 0 !important;
526
+ width: 100% !important;
527
+ height: auto !important;
528
+ min-height: 0 !important;
529
+ box-shadow: none !important;
530
+ }
531
+
532
+ .tool-details {
533
+ width: 100%;
534
+ max-width: 100%;
535
+ padding: 0;
536
+ }
537
+
538
+ .tool-details-container {
539
+ background: #0F172A;
540
+ border-radius: 0;
541
+ padding: 32px;
542
+ margin-bottom: 0;
543
+ max-height: 100vh;
544
+ overflow-y: auto;
545
+ width: 100%;
546
+ }
547
+
548
+ .tool-details-header {
549
+ display: flex;
550
+ align-items: center;
551
+ margin-bottom: 24px;
552
+ justify-content: space-between;
553
+ }
554
+
555
+ .tool-details-title-section {
556
+ display: flex;
557
+ align-items: center;
558
+ }
559
+
560
+ .tool-details-icon {
561
+ font-size: 28px;
562
+ margin-right: 16px;
563
+ }
564
+
565
+ .tool-details-title {
566
+ font-size: 28px;
567
+ font-weight: 700;
568
+ color: white;
569
+ margin: 0;
570
+ }
571
+
572
+ .tool-details-version {
573
+ font-size: 14px;
574
+ color: #94A3B8;
575
+ margin-left: 8px;
576
+ }
577
+
578
+ .tool-details-description {
579
+ font-size: 16px;
580
+ color: #E2E8F0;
581
+ margin-bottom: 32px;
582
+ max-width: 800px;
583
+ }
584
+
585
+ .tool-details-section-title {
586
+ font-size: 18px;
587
+ font-weight: 600;
588
+ color: white;
589
+ margin: 32px 0 16px 0;
590
+ }
591
+
592
+ .tool-details-features {
593
+ display: flex;
594
+ flex-direction: column;
595
+ gap: 12px;
596
+ margin-bottom: 32px;
597
+ }
598
+
599
+ .tool-details-feature {
600
+ display: flex;
601
+ align-items: center;
602
+ margin-bottom: 0;
603
+ }
604
+
605
+ .tool-details-feature-icon {
606
+ color: #38BDF8;
607
+ margin-right: 12px;
608
+ font-size: 16px;
609
+ }
610
+
611
+ .tool-details-feature-text {
612
+ color: #E2E8F0;
613
+ font-size: 16px;
614
+ }
615
+
616
+ .tool-details-screenshots {
617
+ display: grid;
618
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
619
+ gap: 16px;
620
+ margin-top: 16px;
621
+ }
622
+
623
+ .tool-details-screenshot {
624
+ width: 100%;
625
+ border-radius: 8px;
626
+ overflow: hidden;
627
+ }
628
+
629
+ .tool-details-video {
630
+ width: 100%;
631
+ aspect-ratio: 16/9;
632
+ border-radius: 8px;
633
+ overflow: hidden;
634
+ margin-top: 16px;
635
+ background: #1E293B;
636
+ border: none;
637
+ max-width: 800px;
638
+ height: 450px;
639
+ }
640
+
641
+ .back-button {
642
+ background: transparent !important;
643
+ border: none !important;
644
+ color: #94A3B8 !important;
645
+ font-size: 14px !important;
646
+ padding: 8px 16px !important;
647
+ border-radius: 8px !important;
648
+ cursor: pointer !important;
649
+ transition: background-color 0.2s !important;
650
+ text-align: left !important;
651
+ width: auto !important;
652
+ margin-bottom: 24px !important;
653
+ }
654
+
655
+ .back-button:hover {
656
+ background: rgba(255, 255, 255, 0.1) !important;
657
+ }
658
+
659
+ .install-button {
660
+ background: #38BDF8 !important;
661
+ color: #0F172A !important;
662
+ font-weight: 600 !important;
663
+ padding: 12px 24px !important;
664
+ border-radius: 8px !important;
665
+ margin-top: 24px !important;
666
+ width: 100% !important;
667
+ }
668
+
669
+ .install-button:hover {
670
+ background: #0EA5E9 !important;
671
+ }
672
+
673
+ .remove-button {
674
+ background: #EF4444 !important;
675
+ color: white !important;
676
+ font-weight: 600 !important;
677
+ padding: 12px 24px !important;
678
+ border-radius: 8px !important;
679
+ margin-top: 24px !important;
680
+ width: 100% !important;
681
+ border: none !important;
682
+ cursor: pointer !important;
683
+ transition: background-color 0.2s !important;
684
+ }
685
+
686
+ .remove-button:hover {
687
+ background: #DC2626 !important;
688
+ }
689
+
690
+ .tool-details-action .remove-button {
691
+ margin-top: 0 !important;
692
+ padding: 8px 16px !important;
693
+ font-size: 14px !important;
694
+ width: auto !important;
695
+ }
696
+
697
+ /* Confirmation dialog styling */
698
+ .confirmation-dialog {
699
+ background-color: #1E293B;
700
+ border-radius: 8px;
701
+ padding: 24px;
702
+ margin-bottom: 24px;
703
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
704
+ border: 1px solid #2D3748;
705
+ }
706
+
707
+ .confirmation-title {
708
+ font-size: 18px;
709
+ font-weight: 600;
710
+ color: white;
711
+ margin-bottom: 16px;
712
+ }
713
+
714
+ .confirmation-message {
715
+ font-size: 14px;
716
+ color: #E2E8F0;
717
+ margin-bottom: 24px;
718
+ }
719
+
720
+ .confirmation-buttons {
721
+ display: flex;
722
+ justify-content: flex-end;
723
+ gap: 12px;
724
+ }
725
+
726
+ .cancel-button {
727
+ background: #4B5563 !important;
728
+ color: white !important;
729
+ font-weight: 600 !important;
730
+ padding: 8px 16px !important;
731
+ border-radius: 8px !important;
732
+ border: none !important;
733
+ cursor: pointer !important;
734
+ transition: background-color 0.2s !important;
735
+ }
736
+
737
+ .cancel-button:hover {
738
+ background: #374151 !important;
739
+ }
740
+
741
+ .confirm-button {
742
+ background: #EF4444 !important;
743
+ color: white !important;
744
+ font-weight: 600 !important;
745
+ padding: 8px 16px !important;
746
+ border-radius: 8px !important;
747
+ border: none !important;
748
+ cursor: pointer !important;
749
+ transition: background-color 0.2s !important;
750
+ }
751
+
752
+ .confirm-button:hover {
753
+ background: #DC2626 !important;
754
+ }
755
+
756
+ /* Modal styling */
757
+ #installation-modal {
758
+ max-width: 500px;
759
+ border-radius: 8px;
760
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
761
+ }
762
+
763
+ #installation-status {
764
+ font-size: 18px;
765
+ font-weight: 600;
766
+ margin-bottom: 12px;
767
+ padding-bottom: 8px;
768
+ border-bottom: 1px solid #e5e7eb;
769
+ }
770
+
771
+ #installation-result {
772
+ font-size: 14px;
773
+ margin-bottom: 20px;
774
+ white-space: pre-wrap;
775
+ max-height: 300px;
776
+ overflow-y: auto;
777
+ }
778
+
779
+ #close-modal-btn {
780
+ background-color: #6B7280;
781
+ color: white;
782
+ border: none;
783
+ padding: 8px 16px;
784
+ border-radius: 4px;
785
+ cursor: pointer;
786
+ font-weight: 500;
787
+ transition: background-color 0.2s;
788
+ margin-top: 12px;
789
+ }
790
+
791
+ #close-modal-btn:hover {
792
+ background-color: #4B5563;
793
+ }
794
+
795
+ /* Installation result styling */
796
+ #installation-result-container {
797
+ margin-bottom: 20px;
798
+ border: 1px solid #2D3748;
799
+ border-radius: 8px;
800
+ padding: 16px;
801
+ background-color: #1E293B;
802
+ max-width: 100%;
803
+ position: relative;
804
+ z-index: 100;
805
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
806
+ margin-top: 0;
807
+ }
808
+
809
+ #installation-status {
810
+ font-size: 18px;
811
+ font-weight: 600;
812
+ margin-bottom: 12px;
813
+ padding-bottom: 8px;
814
+ border-bottom: 1px solid #2D3748;
815
+ }
816
+
817
+ #installation-result {
818
+ font-size: 14px;
819
+ margin-bottom: 20px;
820
+ white-space: pre-wrap;
821
+ max-height: 300px;
822
+ overflow-y: auto;
823
+ }
824
+
825
+ #close-result-btn {
826
+ background-color: #6B7280;
827
+ color: white;
828
+ border: none;
829
+ padding: 8px 16px;
830
+ border-radius: 4px;
831
+ cursor: pointer;
832
+ font-weight: 500;
833
+ transition: background-color 0.2s;
834
+ margin-top: 12px;
835
+ }
836
+
837
+ #close-result-btn:hover {
838
+ background-color: #4B5563;
839
+ }
840
+
841
+ .tool-details-action {
842
+ display: flex;
843
+ align-items: center;
844
+ }
845
+
846
+ .tool-details-action .install-button {
847
+ margin-top: 0 !important;
848
+ padding: 8px 16px !important;
849
+ font-size: 14px !important;
850
+ width: auto !important;
851
+ }
852
+ """
853
+
854
+ with gr.Blocks(theme=gr.themes.Default(), css=css) as app:
855
+ # Define state variables
856
+ marketplace_tools_state = gr.State([])
857
+ current_tool_details_state = gr.State(None)
858
+
859
+ # Define event handlers first, before any references to them
860
+ def show_tools():
861
+ # Create updates to hide all tool containers
862
+ tool_container_updates = [gr.update(visible=False) for _ in tool_containers]
863
+
864
+ # Generate fresh HTML for tools grid based on current tools
865
+ tools_html = ''
866
+ for tool_id, tool in self.tools.items():
867
+ # Create HTML for each tool card
868
+ tools_html += f'''
869
+ <div class="tool-card" id="tool-card-{tool_id}" onclick="document.getElementById('tool-btn-{tool_id}').click()">
870
+ <div class="tool-card-img">
871
+ {tool.icon}
872
+ </div>
873
+ <div class="tool-card-content">
874
+ <h3 class="tool-card-title">{tool.name}</h3>
875
+ <p class="tool-card-description">{tool.description if hasattr(tool, 'description') else ""}</p>
876
+ </div>
877
+ </div>
878
+ '''
879
+
880
+ # Add "Add New Tool" button
881
+ tools_html += '''
882
+ <div class="tool-card add-tool-card" onclick="document.getElementById('add-tool-btn').click()">
883
+ <div class="add-tool-icon">+</div>
884
+ <div>Add New Tool from Marketplace</div>
885
+ </div>
886
+ '''
887
+
888
+ # Create HTML update for tools grid
889
+ tools_html_component = gr.HTML(f'<div class="tool-grid">{tools_html}</div>')
890
+
891
+ return [
892
+ gr.update(visible=True), # tools_content
893
+ gr.update(visible=False), # marketplace_content
894
+ gr.update(visible=False), # settings_content
895
+ gr.update(visible=False), # tool_interfaces
896
+ gr.update(visible=False), # tool_details_content
897
+ gr.update(variant="primary"), # tools_btn
898
+ gr.update(variant="secondary"), # marketplace_btn
899
+ gr.update(variant="secondary"), # settings_btn,
900
+ tools_html_component, # tools_html_component
901
+ gr.update(value="") # tool_interfaces_html
902
+ ] + tool_container_updates
903
+
904
+ def show_marketplace():
905
+ # Fetch marketplace tools
906
+ tools = self.fetch_marketplace_tools()
907
+
908
+ # Create HTML for marketplace grid
909
+ html = '<div class="tool-grid">'
910
+
911
+ for i, tool in enumerate(tools):
912
+ # Use a unique ID for each card based on the tool ID
913
+ # Add a class to indicate if the tool is installed
914
+ installed_class = "installed" if tool.get('is_installed', False) else ""
915
+
916
+ # Add an indicator for installed tools
917
+ installed_indicator = '<div class="installed-indicator">✓ Installed</div>' if tool.get('is_installed', False) else ''
918
+
919
+ html += f'''
920
+ <div class="tool-card {installed_class}" id="card-{tool['id']}" onclick="document.getElementById('marketplace-tool-btn-{i}').click()">
921
+ <div class="tool-card-img">
922
+ {tool['icon']}
923
+ </div>
924
+ <div class="tool-card-content">
925
+ <h3 class="tool-card-title">{tool['name']}</h3>
926
+ <p class="tool-card-description">{tool['description']}</p>
927
+ {installed_indicator}
928
+ </div>
929
+ </div>
930
+ '''
931
+
932
+ html += '</div>'
933
+
934
+ # Hide installation result container and confirmation dialog
935
+ installation_result_container_update = gr.update(visible=False)
936
+ confirmation_dialog_update = gr.update(visible=False)
937
+
938
+ # Create updates for all tool containers
939
+ tool_container_updates = [gr.update(visible=False) for _ in tool_containers]
940
+
941
+ # Update marketplace_tools_state with the latest tools
942
+ # This ensures the marketplace shows the current state of available tools
943
+
944
+ return [
945
+ gr.update(visible=False), # tools_content
946
+ gr.update(visible=True), # marketplace_content
947
+ gr.update(visible=False), # settings_content
948
+ gr.update(visible=False), # tool_interfaces
949
+ gr.update(visible=False), # tool_details_content
950
+ gr.update(variant="secondary"), # tools_btn
951
+ gr.update(variant="primary"), # marketplace_btn
952
+ gr.update(variant="secondary"), # settings_btn
953
+ tools, # marketplace_tools_state - updated with latest tools
954
+ html, # marketplace_html
955
+ installation_result_container_update, # installation_result_container
956
+ confirmation_dialog_update, # confirmation_dialog
957
+ gr.update(value="") # tool_interfaces_html
958
+ ] + tool_container_updates
959
+
960
+ def show_settings():
961
+ # Create updates to hide all tool containers
962
+ tool_container_updates = [gr.update(visible=False) for _ in tool_containers]
963
+
964
+ return [
965
+ gr.update(visible=False), # tools_content
966
+ gr.update(visible=False), # marketplace_content
967
+ gr.update(visible=True), # settings_content
968
+ gr.update(visible=False), # tool_interfaces
969
+ gr.update(visible=False), # tool_details_content
970
+ gr.update(variant="secondary"), # tools_btn
971
+ gr.update(variant="secondary"), # marketplace_btn
972
+ gr.update(variant="primary"), # settings_btn
973
+ gr.update(value="") # tool_interfaces_html
974
+ ] + tool_container_updates
975
+
976
+ def show_tool(tool_id=None):
977
+ # Import gradio at the beginning of the function
978
+ import gradio as gr
979
+
980
+ # Check if the tool exists
981
+ if tool_id not in self.tools:
982
+ print(f"Tool {tool_id} not found")
983
+ return show_tools()
984
+
985
+ # Get the tool
986
+ tool = self.tools[tool_id]
987
+
988
+ # Check if this is a newly installed tool that needs special handling
989
+ if hasattr(tool, 'newly_installed') and tool.newly_installed:
990
+ print(f"Dynamically creating UI for newly installed tool: {tool_id}")
991
+
992
+ try:
993
+ # Create a message to inform the user that we're loading the tool
994
+ loading_message = f"""
995
+ <div style="text-align: center; padding: 20px;">
996
+ <h2>Loading {tool.name}...</h2>
997
+ <p>Please wait while we set up the tool interface.</p>
998
+ </div>
999
+ """
1000
+
1001
+ # First, return a loading message
1002
+ loading_updates = [
1003
+ gr.update(visible=False), # tools_content
1004
+ gr.update(visible=False), # marketplace_content
1005
+ gr.update(visible=False), # settings_content
1006
+ gr.update(visible=True), # tool_interfaces
1007
+ gr.update(visible=False), # tool_details_content
1008
+ gr.update(variant="secondary"), # tools_btn
1009
+ gr.update(variant="secondary"), # marketplace_btn
1010
+ gr.update(variant="secondary"), # settings_btn
1011
+ gr.update(value=loading_message) # tool_interfaces_html
1012
+ ] + [gr.update(visible=False) for _ in tool_containers]
1013
+
1014
+ # Instead of trying to create the UI dynamically, which can cause issues with Gradio components,
1015
+ # we'll create a simple HTML interface that provides basic functionality and instructions
1016
+
1017
+ # Get the tool class name and module for display
1018
+ tool_class_name = tool.ui_class.__name__
1019
+ tool_module_name = tool.ui_module
1020
+
1021
+ # Create a friendly HTML interface
1022
+ tool_html = f"""
1023
+ <div style="text-align: center; padding: 20px;">
1024
+ <h2>{tool.name} is Ready!</h2>
1025
+ <p>This tool has been successfully installed.</p>
1026
+ <p>For the best experience with this tool, please restart the application.</p>
1027
+ <p>After restarting, you'll be able to use all the features of this tool.</p>
1028
+ <!--<div style="margin-top: 20px;">
1029
+ <button onclick="window.location.reload()" style="background-color: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px;">Refresh Page</button>
1030
+ </div>-->
1031
+ </div>
1032
+ """
1033
+
1034
+ # Remove the newly_installed flag so we don't try to create the UI again
1035
+ delattr(tool, 'newly_installed')
1036
+
1037
+ # Return the HTML interface
1038
+ return [
1039
+ gr.update(visible=False), # tools_content
1040
+ gr.update(visible=False), # marketplace_content
1041
+ gr.update(visible=False), # settings_content
1042
+ gr.update(visible=True), # tool_interfaces
1043
+ gr.update(visible=False), # tool_details_content
1044
+ gr.update(variant="secondary"), # tools_btn
1045
+ gr.update(variant="secondary"), # marketplace_btn
1046
+ gr.update(variant="secondary"), # settings_btn
1047
+ gr.update(value=tool_html) # tool_interfaces_html
1048
+ ] + [gr.update(visible=False) for _ in tool_containers]
1049
+ except Exception as e:
1050
+ print(f"Error creating UI for newly installed tool: {e}")
1051
+ import traceback
1052
+ traceback.print_exc()
1053
+
1054
+ # If there's an error, show a message asking the user to restart
1055
+ restart_message = f"""
1056
+ <div style="text-align: center; padding: 20px;">
1057
+ <h2>Error Loading {tool.name}</h2>
1058
+ <p>There was an error setting up the tool interface:</p>
1059
+ <pre style="text-align: left; background: #f0f0f0; padding: 10px; border-radius: 5px; overflow: auto;">{str(e)}</pre>
1060
+ <p>Please restart the application to use this tool.</p>
1061
+ <div style="margin-top: 20px;">
1062
+ <button onclick="window.location.reload()" style="background-color: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px;">Refresh Page</button>
1063
+ </div>
1064
+ </div>
1065
+ """
1066
+
1067
+ return [
1068
+ gr.update(visible=False), # tools_content
1069
+ gr.update(visible=False), # marketplace_content
1070
+ gr.update(visible=False), # settings_content
1071
+ gr.update(visible=True), # tool_interfaces
1072
+ gr.update(visible=False), # tool_details_content
1073
+ gr.update(variant="secondary"), # tools_btn
1074
+ gr.update(variant="secondary"), # marketplace_btn
1075
+ gr.update(variant="secondary"), # settings_btn
1076
+ gr.update(value=restart_message) # tool_interfaces_html
1077
+ ] + [gr.update(visible=False) for _ in tool_containers]
1078
+
1079
+ # For existing tools with proper UI containers
1080
+ # Create a list of visibility updates for all tool containers
1081
+ updates = []
1082
+
1083
+ for i, container in enumerate(tool_containers):
1084
+ # Get the tool_id for this container
1085
+ container_tool_id = None
1086
+ for tid, t in self.tools.items():
1087
+ if hasattr(t, 'ui_container') and t.ui_container == container:
1088
+ container_tool_id = tid
1089
+ break
1090
+
1091
+ # Set visibility based on whether this is the selected tool
1092
+ if container_tool_id == tool_id:
1093
+ updates.append(gr.update(visible=True))
1094
+ else:
1095
+ updates.append(gr.update(visible=False))
1096
+
1097
+ return [
1098
+ gr.update(visible=False), # tools_content
1099
+ gr.update(visible=False), # marketplace_content
1100
+ gr.update(visible=False), # settings_content
1101
+ gr.update(visible=True), # tool_interfaces
1102
+ gr.update(visible=False), # tool_details_content
1103
+ gr.update(variant="secondary"), # tools_btn
1104
+ gr.update(variant="secondary"), # marketplace_btn
1105
+ gr.update(variant="secondary"), # settings_btn
1106
+ gr.update(value="") # tool_interfaces_html
1107
+ ] + updates
1108
+
1109
+ def show_tool_details(tool_id):
1110
+ # Fetch tool details
1111
+ tool = self.fetch_tool_details(tool_id)
1112
+
1113
+ # Create updates to hide all tool containers
1114
+ tool_container_updates = [gr.update(visible=False) for _ in tool_containers]
1115
+
1116
+ if not tool:
1117
+ return [
1118
+ gr.update(visible=False), # tools_content
1119
+ gr.update(visible=True), # marketplace_content
1120
+ gr.update(visible=False), # settings_content
1121
+ gr.update(visible=False), # tool_interfaces
1122
+ gr.update(visible=False), # tool_details_content
1123
+ gr.update(variant="secondary"), # tools_btn
1124
+ gr.update(variant="primary"), # marketplace_btn
1125
+ gr.update(variant="secondary"), # settings_btn
1126
+ None, # current_tool_details_state
1127
+ "", # tool_details_html
1128
+ gr.update(visible=False), # installation_result_container
1129
+ gr.update(visible=False), # confirmation_dialog
1130
+ gr.update(value="") # tool_interfaces_html
1131
+ ] + tool_container_updates
1132
+
1133
+ # Create features HTML
1134
+ features_html = "".join([
1135
+ f'<div class="tool-details-feature"><span class="tool-details-feature-icon">✓</span><span class="tool-details-feature-text">{feature}</span></div>'
1136
+ for feature in tool.get('features', [])
1137
+ ])
1138
+
1139
+ # Create demo video HTML
1140
+ demo_video_html = f'<iframe class="tool-details-video" src="{tool.get("demo_video", "https://www.youtube.com/embed/dQw4w9WgXcQ")}" frameborder="0" allowfullscreen></iframe>' if True else ''
1141
+
1142
+ # Check if the tool is already installed
1143
+ # Use the is_installed flag if available, otherwise check self.tools
1144
+ is_installed = tool.get('is_installed', False) or tool_id in self.tools
1145
+
1146
+ # Determine which button to show based on installation status
1147
+ if is_installed:
1148
+ action_button = f'<button id="remove-tool-btn-inline" class="remove-button" onclick="document.getElementById(\'remove-btn\').click()">Remove Tool</button>'
1149
+ else:
1150
+ action_button = f'<button id="add-tool-btn-inline" class="install-button" onclick="document.getElementById(\'install-btn\').click()">Add Tool</button>'
1151
+
1152
+ # Create HTML for tool details
1153
+ html = f'''
1154
+ <div class="tool-details-container">
1155
+ <div class="tool-details">
1156
+ <div class="tool-details-header">
1157
+ <div class="tool-details-title-section">
1158
+ <div class="tool-details-icon">{tool['icon']}</div>
1159
+ <h2 class="tool-details-title">{tool['name']} <span class="tool-details-version">v{tool['version']}</span></h2>
1160
+ </div>
1161
+ <div class="tool-details-action">
1162
+ {action_button}
1163
+ </div>
1164
+ </div>
1165
+ <p class="tool-details-description">{tool['description']}</p>
1166
+
1167
+ <h3 class="tool-details-section-title">Features</h3>
1168
+ <div class="tool-details-features">
1169
+ {features_html}
1170
+ </div>
1171
+
1172
+ <h3 class="tool-details-section-title">Video</h3>
1173
+ {demo_video_html}
1174
+ </div>
1175
+ </div>
1176
+ '''
1177
+
1178
+ print(f"Setting current_tool_details_state to: {tool}")
1179
+ return [
1180
+ gr.update(visible=False), # tools_content
1181
+ gr.update(visible=False), # marketplace_content
1182
+ gr.update(visible=False), # settings_content
1183
+ gr.update(visible=False), # tool_interfaces
1184
+ gr.update(visible=True), # tool_details_content
1185
+ gr.update(variant="secondary"), # tools_btn
1186
+ gr.update(variant="secondary"), # marketplace_btn
1187
+ gr.update(variant="secondary"), # settings_btn
1188
+ tool, # current_tool_details_state
1189
+ html, # tool_details_html
1190
+ gr.update(visible=False), # installation_result_container
1191
+ gr.update(visible=False), # confirmation_dialog
1192
+ gr.update(value="") # tool_interfaces_html
1193
+ ] + tool_container_updates
1194
+
1195
+ def install_selected_tool(tool_details):
1196
+ print(f"Installing tool: {tool_details}")
1197
+
1198
+ if not tool_details:
1199
+ print("No tool details provided")
1200
+ return [
1201
+ gr.update(visible=True), # installation_result_container
1202
+ "⚠️ Installation Failed", # installation_status
1203
+ "No tool selected to install." # installation_result
1204
+ ]
1205
+
1206
+ try:
1207
+ # Install the tool
1208
+ result = self.install_tool(tool_details['id'])
1209
+
1210
+ if "Error" in result:
1211
+ return [
1212
+ gr.update(visible=True), # installation_result_container
1213
+ "⚠️ Installation Failed", # installation_status
1214
+ result # installation_result
1215
+ ]
1216
+ else:
1217
+ # If installation was successful, we'll show a success message
1218
+ # The user can click "Close" to see the installation result
1219
+ # and then navigate to the tools page to see the new tool
1220
+ return [
1221
+ gr.update(visible=True), # installation_result_container
1222
+ "✅ Installation Successful", # installation_status
1223
+ result # installation_result
1224
+ ]
1225
+ except Exception as e:
1226
+ print(f"Error installing tool: {e}")
1227
+ return [
1228
+ gr.update(visible=True), # installation_result_container
1229
+ "⚠️ Installation Failed", # installation_status
1230
+ f"Error installing tool: {e}" # installation_result
1231
+ ]
1232
+
1233
+ # Create components after defining the functions
1234
+ with gr.Row():
1235
+ # Sidebar
1236
+ with gr.Column(elem_id="sidebar", scale=1):
1237
+ gr.Markdown("AI TOOLS", elem_classes="title")
1238
+ tools_btn = gr.Button("Tools", elem_classes="nav-button", variant="primary")
1239
+ marketplace_btn = gr.Button("Marketplace", elem_classes="nav-button", variant="secondary")
1240
+ settings_btn = gr.Button("Settings", elem_classes="nav-button", variant="secondary")
1241
+ gr.Markdown(f"version: {self.version}", elem_classes="version-text")
1242
+
1243
+ # Main content
1244
+ with gr.Column(elem_id="main-content", scale=4):
1245
+ # Define all content containers
1246
+ tools_content = gr.Column(visible=True)
1247
+ marketplace_content = gr.Column(visible=False)
1248
+ settings_content = gr.Column(visible=False)
1249
+ tool_interfaces = gr.Column(visible=False)
1250
+ tool_details_content = gr.Column(visible=False)
1251
+
1252
+ # Fill tools content
1253
+ with tools_content:
1254
+ gr.Markdown("Tools", elem_classes="page-title")
1255
+
1256
+ tool_buttons = []
1257
+ tool_ids = [] # Store tool_ids in the same order as tool_buttons
1258
+
1259
+ # Create HTML for all tools
1260
+ tools_html = ''
1261
+
1262
+ for tool_id, tool in self.tools.items():
1263
+ # Create HTML for each tool card
1264
+ tools_html += f'''
1265
+ <div class="tool-card" id="tool-card-{tool_id}" onclick="document.getElementById('tool-btn-{tool_id}').click()">
1266
+ <div class="tool-card-img">
1267
+ {tool.icon}
1268
+ </div>
1269
+ <div class="tool-card-content">
1270
+ <h3 class="tool-card-title">{tool.name}</h3>
1271
+ <p class="tool-card-description">{tool.description if hasattr(tool, 'description') else ""}</p>
1272
+ </div>
1273
+ </div>
1274
+ '''
1275
+
1276
+ # Create hidden buttons for tool selection
1277
+ btn = gr.Button(f"Select {tool.name}", visible=False, elem_id=f"tool-btn-{tool_id}")
1278
+ tool_buttons.append(btn)
1279
+ tool_ids.append(tool_id)
1280
+
1281
+ # Add "Add New Tool" button
1282
+ tools_html += '''
1283
+ <div class="tool-card add-tool-card" onclick="document.getElementById('add-tool-btn').click()">
1284
+ <div class="add-tool-icon">+</div>
1285
+ <div>Add New Tool from Marketplace</div>
1286
+ </div>
1287
+ '''
1288
+
1289
+ # Render the HTML inside a div with the tool-grid class
1290
+ tools_html_component = gr.HTML(f'<div class="tool-grid">{tools_html}</div>')
1291
+
1292
+ # Create hidden add tool button
1293
+ add_tool_btn = gr.Button("Add New Tool", visible=False, elem_id="add-tool-btn")
1294
+
1295
+ # Fill marketplace content
1296
+ with marketplace_content:
1297
+ gr.Markdown("Marketplace", elem_classes="page-title")
1298
+ marketplace_html = gr.HTML("Loading marketplace tools...")
1299
+
1300
+ # Create hidden buttons for marketplace tool details with unique IDs
1301
+ tool_buttons_container = gr.Column(visible=False)
1302
+
1303
+ # Create hidden buttons for marketplace tools
1304
+ marketplace_tool_buttons = []
1305
+ for i, tool in enumerate(self.fetch_marketplace_tools()):
1306
+ tool_btn = gr.Button(f"View {tool['name']}", visible=False, elem_id=f"marketplace-tool-btn-{i}")
1307
+ marketplace_tool_buttons.append((tool['id'], tool_btn))
1308
+
1309
+ # The click handlers for marketplace tool buttons will be set up after all components are defined
1310
+
1311
+ # Fill tool details content
1312
+ with tool_details_content:
1313
+ # Installation result section at the top
1314
+ with gr.Column(elem_id="installation-result-container", elem_classes="installation-result-container", visible=False) as installation_result_container:
1315
+ installation_status = gr.Markdown("Ready to install", elem_id="installation-status")
1316
+ installation_result = gr.Markdown("Click the Install Tool button to install the selected tool.", elem_id="installation-result")
1317
+ close_result_btn = gr.Button("Close", elem_id="close-result-btn")
1318
+
1319
+ # Confirmation dialog for tool removal
1320
+ with gr.Column(elem_id="confirmation-dialog", elem_classes="confirmation-dialog", visible=False) as confirmation_dialog:
1321
+ gr.Markdown("⚠️ Warning: Remove Tool", elem_id="confirmation-title", elem_classes="confirmation-title")
1322
+ gr.Markdown(
1323
+ "Are you sure you want to remove this tool? This action cannot be undone. "
1324
+ "All data created by this tool will be permanently deleted.",
1325
+ elem_id="confirmation-message",
1326
+ elem_classes="confirmation-message"
1327
+ )
1328
+ with gr.Row(elem_classes="confirmation-buttons"):
1329
+ cancel_btn = gr.Button("Cancel", elem_classes="cancel-button")
1330
+ confirm_btn = gr.Button("Yes, Remove Tool", elem_classes="confirm-button")
1331
+
1332
+ with gr.Row():
1333
+ with gr.Column(scale=1):
1334
+ back_btn = gr.Button("← Back to Marketplace", elem_classes="back-button")
1335
+ with gr.Column(scale=3):
1336
+ gr.Markdown("", elem_classes="page-title")
1337
+
1338
+ # Tool details content
1339
+ tool_details_html = gr.HTML("")
1340
+
1341
+ # Hidden install button (will be triggered by the inline button)
1342
+ install_btn = gr.Button("Add Tool", elem_classes="install-button", elem_id="install-btn", variant="primary", visible=False)
1343
+
1344
+ # Hidden remove button (will be triggered by the inline button)
1345
+ remove_btn = gr.Button("Remove Tool", elem_classes="remove-button", elem_id="remove-btn", variant="primary", visible=False)
1346
+
1347
+ # Add a direct event handler to the install button
1348
+ def debug_install_click():
1349
+ print("Install button clicked directly!")
1350
+ return [gr.update(visible=True), gr.update(value="Install button clicked!")]
1351
+
1352
+ # Add a direct event handler to the remove button to show confirmation dialog
1353
+ def show_confirmation_dialog():
1354
+ print("Remove button clicked - showing confirmation dialog")
1355
+ return gr.update(visible=True)
1356
+
1357
+ # Function to handle tool removal confirmation
1358
+ def remove_selected_tool(tool_details):
1359
+ print(f"Removing tool: {tool_details}")
1360
+
1361
+ if not tool_details:
1362
+ print("No tool details provided")
1363
+ return [
1364
+ gr.update(visible=False), # confirmation_dialog
1365
+ gr.update(visible=True), # installation_result_container
1366
+ "⚠️ Removal Failed", # installation_status
1367
+ "No tool selected to remove." # installation_result
1368
+ ]
1369
+
1370
+ try:
1371
+ # Remove the tool
1372
+ result = self.remove_tool(tool_details['id'])
1373
+
1374
+ if "Error" in result:
1375
+ return [
1376
+ gr.update(visible=False), # confirmation_dialog
1377
+ gr.update(visible=True), # installation_result_container
1378
+ "⚠️ Removal Failed", # installation_status
1379
+ result # installation_result
1380
+ ]
1381
+ else:
1382
+ # After successful removal, show success message
1383
+ # The user will need to click "Back to Marketplace" manually
1384
+ # This avoids issues with dynamic UI updates
1385
+ return [
1386
+ gr.update(visible=False), # confirmation_dialog
1387
+ gr.update(visible=True), # installation_result_container
1388
+ "✅ Removal Successful", # installation_status
1389
+ f"{result} - Click 'Back to Marketplace' to return. You may need to restart the application to see all changes." # installation_result
1390
+ ]
1391
+ except Exception as e:
1392
+ print(f"Error removing tool: {e}")
1393
+ return [
1394
+ gr.update(visible=False), # confirmation_dialog
1395
+ gr.update(visible=True), # installation_result_container
1396
+ "⚠️ Removal Failed", # installation_status
1397
+ f"Error removing tool: {e}" # installation_result
1398
+ ]
1399
+
1400
+ # Function to cancel removal
1401
+ def cancel_removal():
1402
+ return gr.update(visible=False)
1403
+
1404
+ debug_output = gr.Textbox(label="Debug Output", visible=False)
1405
+
1406
+ # Set up event handlers for buttons
1407
+ install_btn.click(
1408
+ fn=install_selected_tool,
1409
+ inputs=[current_tool_details_state],
1410
+ outputs=[installation_result_container, installation_status, installation_result]
1411
+ )
1412
+ remove_btn.click(fn=show_confirmation_dialog, outputs=confirmation_dialog)
1413
+
1414
+ # Set up confirmation dialog buttons
1415
+ cancel_btn.click(fn=cancel_removal, outputs=confirmation_dialog)
1416
+ confirm_btn.click(
1417
+ fn=remove_selected_tool,
1418
+ inputs=[current_tool_details_state],
1419
+ outputs=[confirmation_dialog, installation_result_container, installation_status, installation_result]
1420
+ )
1421
+
1422
+ # Fill settings content
1423
+ with settings_content:
1424
+ gr.Markdown("Settings", elem_classes="page-title")
1425
+ gr.Markdown("Settings options will appear here...")
1426
+
1427
+ # Fill tool interfaces
1428
+ with tool_interfaces:
1429
+ # Add an HTML component to display messages for newly installed tools
1430
+ tool_interfaces_html = gr.HTML("", elem_id="tool-interfaces-html")
1431
+
1432
+ for tool_id, tool in self.tools.items():
1433
+ with gr.Column(visible=False) as tool_container:
1434
+ tool_ui = tool.ui_class()
1435
+ tool_ui.create_ui()
1436
+ # Store the UI container in the tool object for later reference
1437
+ tool.ui_container = tool_container
1438
+
1439
+ # Set up event handlers after all components are defined
1440
+ # Set up tool button events
1441
+
1442
+ # Collect all tool containers for outputs
1443
+ tool_containers = []
1444
+ for tool_id, tool in self.tools.items():
1445
+ if hasattr(tool, 'ui_container'):
1446
+ tool_containers.append(tool.ui_container)
1447
+
1448
+ # Set up click handlers for marketplace tool buttons
1449
+ for i, (tool_id, tool_btn) in enumerate(marketplace_tool_buttons):
1450
+ # We need to create a separate function for each button to avoid lambda closure issues
1451
+ def make_handler(tid):
1452
+ return lambda: show_tool_details(tid)
1453
+
1454
+ handler = make_handler(tool_id)
1455
+ tool_btn.click(
1456
+ fn=handler,
1457
+ outputs=[
1458
+ tools_content, marketplace_content, settings_content, tool_interfaces,
1459
+ tool_details_content, tools_btn, marketplace_btn, settings_btn,
1460
+ current_tool_details_state, tool_details_html, installation_result_container,
1461
+ confirmation_dialog, tool_interfaces_html
1462
+ ] + tool_containers
1463
+ )
1464
+
1465
+ for i, btn in enumerate(tool_buttons):
1466
+ if isinstance(btn, gr.Button): # Check if it's a button from the tools grid
1467
+ tool_id = tool_ids[i] # Get the corresponding tool_id
1468
+
1469
+ # We need to create a separate function for each button to avoid lambda closure issues
1470
+ def make_handler(tid):
1471
+ return lambda: show_tool(tid)
1472
+
1473
+ handler = make_handler(tool_id)
1474
+ btn.click(
1475
+ fn=handler,
1476
+ outputs=[tools_content, marketplace_content, settings_content, tool_interfaces,
1477
+ tool_details_content, tools_btn, marketplace_btn, settings_btn,
1478
+ tool_interfaces_html] + tool_containers
1479
+ )
1480
+
1481
+ # Set up navigation events
1482
+ tools_btn.click(
1483
+ fn=show_tools,
1484
+ outputs=[tools_content, marketplace_content, settings_content, tool_interfaces,
1485
+ tool_details_content, tools_btn, marketplace_btn, settings_btn,
1486
+ tools_html_component, tool_interfaces_html] + tool_containers
1487
+ )
1488
+
1489
+ marketplace_btn.click(
1490
+ fn=show_marketplace,
1491
+ outputs=[tools_content, marketplace_content, settings_content, tool_interfaces,
1492
+ tool_details_content, tools_btn, marketplace_btn, settings_btn,
1493
+ marketplace_tools_state, marketplace_html, installation_result_container,
1494
+ confirmation_dialog, tool_interfaces_html] + tool_containers
1495
+ )
1496
+
1497
+ settings_btn.click(
1498
+ fn=show_settings,
1499
+ outputs=[tools_content, marketplace_content, settings_content, tool_interfaces,
1500
+ tool_details_content, tools_btn, marketplace_btn, settings_btn,
1501
+ tool_interfaces_html] + tool_containers
1502
+ )
1503
+
1504
+ # Set up add tool button to navigate to marketplace
1505
+ add_tool_btn.click(
1506
+ fn=show_marketplace,
1507
+ outputs=[tools_content, marketplace_content, settings_content, tool_interfaces,
1508
+ tool_details_content, tools_btn, marketplace_btn, settings_btn,
1509
+ marketplace_tools_state, marketplace_html, installation_result_container,
1510
+ confirmation_dialog, tool_interfaces_html] + tool_containers
1511
+ )
1512
+
1513
+ # Set up event handlers for tool details
1514
+ back_btn.click(
1515
+ fn=show_marketplace,
1516
+ outputs=[
1517
+ tools_content, marketplace_content, settings_content, tool_interfaces,
1518
+ tool_details_content, tools_btn, marketplace_btn, settings_btn,
1519
+ marketplace_tools_state, marketplace_html, installation_result_container,
1520
+ confirmation_dialog
1521
+ ] + tool_containers
1522
+ )
1523
+
1524
+ # Define a function to handle closing the installation result and navigating to tools
1525
+ def close_result_and_show_tools():
1526
+ # First hide the installation result container
1527
+ installation_result_update = gr.update(visible=False)
1528
+
1529
+ # Then get the updates from show_tools
1530
+ tools_updates = show_tools()
1531
+
1532
+ # Combine the updates
1533
+ return [installation_result_update] + tools_updates
1534
+
1535
+ # Set up close button for installation result
1536
+ close_result_btn.click(
1537
+ fn=close_result_and_show_tools,
1538
+ outputs=[installation_result_container,
1539
+ tools_content, marketplace_content, settings_content, tool_interfaces,
1540
+ tool_details_content, tools_btn, marketplace_btn, settings_btn,
1541
+ tools_html_component, tool_interfaces_html] + tool_containers
1542
+ )
1543
+
1544
+ return app
1545
+
1546
+ if __name__ == "__main__":
1547
+ ui = MainUI()
1548
+ demo = ui.create_ui()
1549
+ demo.queue() # Add queue for better handling of multiple requests
1550
+ demo.title = "AI Tools Platform" # Add a title
1551
+ demo.launch(
1552
+ debug=True,
1553
+ share=True,
1554
+ server_name="0.0.0.0",
1555
+ server_port=7860
1556
+ )
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ librosa==0.10.1
2
+ numpy==1.26.3
3
+ moviepy==2.1.2
4
+ opencv-python>=4.8.0
5
+ scikit-learn>=1.3.0
6
+ scenedetect~=0.6.5.2
7
+ gradio==4.19.2
8
+ flask==2.3.3
9
+ requests==2.31.0
10
+ rembg
11
+ Pillow
12
+ onnxruntime
tools/beatsyncer/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Beatsyncer package
tools/beatsyncer/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (178 Bytes). View file
 
tools/beatsyncer/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (166 Bytes). View file
 
tools/beatsyncer/__pycache__/beatsyncer_tool.cpython-311.pyc ADDED
Binary file (20.1 kB). View file
 
tools/beatsyncer/__pycache__/beatsyncer_tool.cpython-312.pyc ADDED
Binary file (18.3 kB). View file
 
tools/beatsyncer/__pycache__/beatsyncer_ui.cpython-311.pyc ADDED
Binary file (7.07 kB). View file
 
tools/beatsyncer/__pycache__/beatsyncer_ui.cpython-312.pyc ADDED
Binary file (6.08 kB). View file
 
tools/beatsyncer/beatsyncer_tool.py ADDED
@@ -0,0 +1,470 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import librosa
4
+ import numpy as np
5
+ from moviepy import *
6
+
7
+ class BeatSyncer:
8
+ AUDIO_EXTENSIONS = {'.mp3', '.wav', '.m4a', '.aac', '.ogg', '.flac'}
9
+ VIDEO_EXTENSIONS = {'.mp4', '.avi', '.mov', '.mkv', '.wmv', '.flv'}
10
+
11
+ def __init__(self, audio_filename):
12
+ # Construct the full path to the audio file relative to the script's directory
13
+ script_dir = os.path.dirname(os.path.abspath(__file__))
14
+ input_path = os.path.join(script_dir, audio_filename)
15
+
16
+ # Convert to audio if needed
17
+ if self._is_video_file(input_path):
18
+ print(f"Converting video file '{input_path}' to audio...")
19
+ audio_path = self._convert_to_audio(input_path)
20
+ self.audio_path = audio_path
21
+ elif self._is_audio_file(input_path):
22
+ self.audio_path = input_path
23
+ else:
24
+ raise ValueError(f"Unsupported file format. File must be one of: {self.AUDIO_EXTENSIONS.union(self.VIDEO_EXTENSIONS)}")
25
+
26
+ self.y = None # Audio samples
27
+ self.sr = None # Sample rate
28
+ self.beat_times = None
29
+ self.beat_drop_times = None
30
+
31
+ def _is_audio_file(self, filepath):
32
+ """Check if the file is an audio file based on its extension."""
33
+ return os.path.splitext(filepath)[1].lower() in self.AUDIO_EXTENSIONS
34
+
35
+ def _is_video_file(self, filepath):
36
+ """Check if the file is a video file based on its extension."""
37
+ return os.path.splitext(filepath)[1].lower() in self.VIDEO_EXTENSIONS
38
+
39
+ def _convert_to_audio(self, video_path):
40
+ """Convert a video file to audio and return the path to the audio file."""
41
+ try:
42
+ # Create tmp directory in project root if it doesn't exist
43
+ script_dir = os.path.dirname(os.path.abspath(__file__))
44
+ tmp_dir = os.path.join(script_dir, 'tmp')
45
+ os.makedirs(tmp_dir, exist_ok=True)
46
+
47
+ # Create audio filename in tmp directory
48
+ base_name = os.path.basename(video_path)
49
+ audio_name = os.path.splitext(base_name)[0] + '.mp3'
50
+ audio_path = os.path.join(tmp_dir, audio_name)
51
+
52
+ # Convert video to audio
53
+ video = VideoFileClip(video_path)
54
+ video.audio.write_audiofile(audio_path)
55
+ video.close()
56
+
57
+ return audio_path
58
+ except Exception as e:
59
+ raise ValueError(f"Error converting video to audio: {str(e)}")
60
+
61
+ def load_audio(self, duration_minutes=None):
62
+ """
63
+ Load the audio file using librosa, limiting to duration_minutes if provided.
64
+ """
65
+ try:
66
+ self.sr = 44100 # Set a standard sample rate
67
+ if duration_minutes is not None:
68
+ duration_seconds = duration_minutes * 60
69
+ else:
70
+ duration_seconds = None # Load full audio
71
+ self.y, _ = librosa.load(self.audio_path, sr=self.sr, duration=duration_seconds)
72
+ if self.y is None or len(self.y) == 0:
73
+ raise ValueError("Audio data is empty.")
74
+ duration = len(self.y) / self.sr
75
+ print(f"Loaded audio file '{self.audio_path}' with duration {duration:.2f} seconds.")
76
+ except Exception as e:
77
+ print(f"An error occurred while loading the audio file: {e}")
78
+ raise
79
+
80
+ def detect_beat_drops(self, sensitivity='medium'):
81
+ """
82
+ Improved beat detection using onset_strength_multi and adjusted parameters.
83
+
84
+ Args:
85
+ sensitivity (str): Detection sensitivity level ('low', 'medium', or 'high').
86
+ - low: Less sensitive, detects only major beat drops
87
+ - medium: Balanced sensitivity (default)
88
+ - high: More sensitive, detects subtle beat changes
89
+ """
90
+ print("Detecting beat drops...")
91
+ if self.y is None or len(self.y) == 0:
92
+ raise ValueError("Audio data is empty. Cannot perform beat detection.")
93
+
94
+ # Map sensitivity levels to parameters
95
+ sensitivity_params = {
96
+ 'low': {
97
+ 'hop_length': 512, # Larger hop length for less granular detection
98
+ 'std_multiplier': 1.0, # Higher threshold for beat detection
99
+ 'min_interval': 0.05 # Longer minimum interval between beats
100
+ },
101
+ 'medium': {
102
+ 'hop_length': 256, # Default hop length
103
+ 'std_multiplier': 0.5, # Default threshold
104
+ 'min_interval': 0.02 # Default minimum interval
105
+ },
106
+ 'high': {
107
+ 'hop_length': 128, # Smaller hop length for more granular detection
108
+ 'std_multiplier': 0.25, # Lower threshold for beat detection
109
+ 'min_interval': 0.01 # Shorter minimum interval between beats
110
+ }
111
+ }
112
+
113
+ # Validate and get sensitivity parameters
114
+ sensitivity = sensitivity.lower()
115
+ if sensitivity not in sensitivity_params:
116
+ raise ValueError(f"Invalid sensitivity level. Must be one of: {list(sensitivity_params.keys())}")
117
+
118
+ params = sensitivity_params[sensitivity]
119
+
120
+ # Parameters for onset detection
121
+ hop_length = params['hop_length']
122
+
123
+ params.update({
124
+ "detrend": True,
125
+ "tightness": 100
126
+ })
127
+
128
+ # Compute the onset envelope using onset_strength_multi with adjusted parameters
129
+ onset_env_multi = librosa.onset.onset_strength_multi(
130
+ y=self.y,
131
+ sr=self.sr,
132
+ hop_length=params["hop_length"],
133
+ aggregate=np.median,
134
+ lag=1,
135
+ max_size=1,
136
+ detrend=params["detrend"],
137
+ center=True,
138
+ )
139
+
140
+ # Sum across channels if multi-channel audio
141
+ onset_env = onset_env_multi.mean(axis=0)
142
+
143
+ # Beat tracking with adjusted parameters
144
+ tempo, beats = librosa.beat.beat_track(
145
+ onset_envelope=onset_env,
146
+ sr=self.sr,
147
+ hop_length=params["hop_length"],
148
+ tightness=params["tightness"],
149
+ units='time'
150
+ )
151
+
152
+ # Convert tempo to scalar if it's an array
153
+ tempo = float(np.mean(tempo)) if isinstance(tempo, np.ndarray) else float(tempo)
154
+
155
+ # Adjust tempo if it's unusually high (e.g., over 180 BPM)
156
+ if tempo >= 180:
157
+ tempo /= 2
158
+ print(f"Adjusted tempo: {tempo:.2f} BPM")
159
+ else:
160
+ print(f"Detected tempo: {tempo:.2f} BPM")
161
+
162
+ self.beat_times = beats
163
+
164
+ if len(self.beat_times) == 0:
165
+ raise ValueError("No beats detected in the audio.")
166
+
167
+ # Compute the difference in onset envelope with sensitivity-based threshold
168
+ onset_diff = np.diff(onset_env)
169
+ threshold = np.mean(onset_diff) + params['std_multiplier'] * np.std(onset_diff)
170
+ beat_drop_indices = np.where(onset_diff > threshold)[0] + 1
171
+
172
+ # Convert beat frames to times
173
+ onset_times = librosa.frames_to_time(
174
+ np.arange(len(onset_env)), sr=self.sr, hop_length=hop_length
175
+ )
176
+
177
+ # Map beat drop indices to times
178
+ beat_drop_times = onset_times[beat_drop_indices]
179
+
180
+ # Remove duplicate beat times
181
+ self.beat_drop_times = np.unique(beat_drop_times)
182
+
183
+ # Remove beat drops that are too close together based on sensitivity
184
+ min_interval = params['min_interval']
185
+ filtered_beat_drop_times = [self.beat_drop_times[0]]
186
+ for bd_time in self.beat_drop_times[1:]:
187
+ if bd_time - filtered_beat_drop_times[-1] >= min_interval:
188
+ filtered_beat_drop_times.append(bd_time)
189
+ self.beat_drop_times = np.array(filtered_beat_drop_times)
190
+
191
+ print(f"Detected {len(self.beat_drop_times)} beat drops with {sensitivity} sensitivity.")
192
+
193
+ def _get_media_files(self, media_dir):
194
+ """
195
+ Get list of media files from the directory.
196
+ Returns a shuffled list of absolute paths to media files.
197
+ """
198
+ supported_extensions = ('.mp4', '.avi', '.mov', '.png', '.jpg', '.jpeg', '.gif', '.mkv')
199
+ media_files = []
200
+
201
+ # Walk through directory
202
+ for root, _, files in os.walk(media_dir):
203
+ for file in files:
204
+ if file.lower().endswith(supported_extensions):
205
+ full_path = os.path.join(root, file)
206
+ media_files.append(full_path)
207
+
208
+ if not media_files:
209
+ raise ValueError(f"No supported media files found in directory: {media_dir}")
210
+
211
+ # Shuffle the media files list
212
+ random.shuffle(media_files)
213
+ print(f"Found and shuffled {len(media_files)} media files")
214
+
215
+ return media_files
216
+
217
+ def _calculate_text_duration(self, text):
218
+ """
219
+ Calculate the duration a text should be displayed based on its word count.
220
+
221
+ Args:
222
+ text (str): The text to be displayed
223
+
224
+ Returns:
225
+ float: Duration in seconds
226
+ """
227
+ # Count words (split by whitespace)
228
+ word_count = len(text.split())
229
+
230
+ # Base duration calculation:
231
+ # - Minimum duration: 2 seconds
232
+ # - Add 0.5 seconds per word
233
+ # - Maximum duration: 8 seconds
234
+ duration = min(max(2, word_count * 0.5), 8)
235
+
236
+ return duration
237
+
238
+ def _wrap_text(self, text, max_chars_per_line=30):
239
+ """
240
+ Wrap text to ensure it fits nicely on screen.
241
+
242
+ Args:
243
+ text (str): Text to wrap
244
+ max_chars_per_line (int): Maximum characters per line
245
+
246
+ Returns:
247
+ str: Wrapped text with newlines and increased line spacing
248
+ """
249
+ words = text.split()
250
+ lines = []
251
+ current_line = []
252
+ current_length = 0
253
+
254
+ for word in words:
255
+ word_length = len(word)
256
+ if current_length + word_length + len(current_line) <= max_chars_per_line:
257
+ current_line.append(word)
258
+ current_length += word_length
259
+ else:
260
+ lines.append(' '.join(current_line))
261
+ current_line = [word]
262
+ current_length = word_length
263
+
264
+ if current_line:
265
+ lines.append(' '.join(current_line))
266
+
267
+ # Add extra line spacing by using double newlines
268
+ return '\n'.join(lines)
269
+
270
+ def sync_with_media_directory(self, media_dir, output_path, duration_minutes=None, video_size=None, dark_overlay=0.3,
271
+ text_overlays=None):
272
+ """
273
+ Create a video by syncing media files with beat drops.
274
+ Args:
275
+ media_dir: Directory containing images and videos
276
+ output_path: Path where the output video will be saved
277
+ duration_minutes: Desired total duration in minutes
278
+ video_size: Tuple of (width, height) for output video
279
+ dark_overlay: Opacity of dark overlay (0.0 to 1.0, where 1.0 is completely black)
280
+ text_overlays: List of text strings or tuples (text, duration_seconds).
281
+ If only text is provided, duration will be calculated based on word count.
282
+ """
283
+ print("Creating video from media files...")
284
+ media_files = self._get_media_files(media_dir)
285
+
286
+ if not media_files:
287
+ raise ValueError(f"No media files found in directory: {media_dir}")
288
+
289
+ # Prepare clips with dark theme
290
+ clips = []
291
+
292
+ # Set desired_total_duration
293
+ if duration_minutes is not None:
294
+ desired_total_duration = duration_minutes * 60 # Convert minutes to seconds
295
+ else:
296
+ desired_total_duration = len(self.y) / self.sr # Use the length of the audio
297
+
298
+ # Limit the beat_drop_times to the desired duration
299
+ beat_drop_times = self.beat_drop_times[self.beat_drop_times <= desired_total_duration]
300
+
301
+ if len(beat_drop_times) == 0:
302
+ raise ValueError("No beat drops detected within the desired duration.")
303
+
304
+ # Include the start (0) and end times to cover the entire duration
305
+ beat_times_full = np.concatenate(([0], beat_drop_times, [desired_total_duration]))
306
+
307
+ num_media = len(media_files)
308
+ media_index = 0
309
+
310
+ synced_clips = []
311
+
312
+ # Define the desired clip length for initial blank space
313
+ initial_clip_duration = 2 # 2 seconds for each clip in the initial blank space
314
+ base_scale = 1.2 # resized factor to make clips larger than the video size
315
+ zoom_factor = 0.009 # Zoom factor for zoom effects
316
+
317
+ # Loop over intervals between beat times to cover entire duration
318
+ for i in range(len(beat_times_full) - 1):
319
+ start_time = beat_times_full[i]
320
+ end_time = beat_times_full[i + 1]
321
+ interval_duration = end_time - start_time
322
+
323
+ if i == 0 and interval_duration > initial_clip_duration:
324
+ # Handle the initial blank space with multiple clips
325
+ num_initial_clips = int(np.ceil(interval_duration / initial_clip_duration))
326
+ for j in range(num_initial_clips):
327
+ clip_start_time = start_time + j * initial_clip_duration
328
+ clip_end_time = min(clip_start_time + initial_clip_duration, end_time)
329
+ clip_duration = clip_end_time - clip_start_time
330
+
331
+ media_file = media_files[media_index % num_media]
332
+ media_index += 1
333
+
334
+ if media_file.lower().endswith(('.mp4', '.avi', '.mov')):
335
+ # It's a video file
336
+ clip = VideoFileClip(media_file).subclipped(0, clip_duration).resized(base_scale)
337
+
338
+ else:
339
+ # It's an image file
340
+ clip = ImageClip(media_file).with_duration(clip_duration).resized(base_scale)
341
+
342
+
343
+ # cropped the clip to the desired video size
344
+ clip = clip.cropped(width=video_size[0], height=video_size[1], x_center=clip.w / 2,
345
+ y_center=clip.h / 2)
346
+
347
+ # Set the start time of the clip
348
+ clip = clip.with_start(clip_start_time)
349
+
350
+ # Apply random zoom in or zoom out effect with slower zoom
351
+ zoom_type = random.choice(['zoom_in', 'zoom_out', None])
352
+ # if zoom_type == 'zoom_in':
353
+ # # Slow down zoom in effect
354
+ # clip = clip.with_effects([vfx.Resize(lambda t: 1 + zoom_factor * (t / clip.duration))])
355
+ # elif zoom_type == 'zoom_out':
356
+ # # Slow down zoom out effect
357
+ # clip = clip.with_effects([vfx.Resize(lambda t: 1 + zoom_factor * (1 - t / clip.duration))])
358
+
359
+ # Add random transition to the clip
360
+ if synced_clips:
361
+ transition_type = random.choice(['crossfadein', 'fadein', None])
362
+ if transition_type == 'crossfadein':
363
+ # clip = clip.crossfadein(0.1)
364
+ print()
365
+ elif transition_type == 'fadein':
366
+ # clip = clip.fadein(0.1)
367
+ print()
368
+
369
+ synced_clips.append(clip)
370
+ else:
371
+ # Handle regular intervals between beat drops
372
+ media_file = media_files[media_index % num_media]
373
+ media_index += 1
374
+
375
+ if media_file.lower().endswith(('.mp4', '.avi', '.mov')):
376
+ # It's a video file
377
+ clip = VideoFileClip(media_file).subclipped(0, interval_duration).resized(base_scale)
378
+
379
+
380
+ else:
381
+ # It's an image file
382
+ clip = ImageClip(media_file).with_duration(interval_duration).resized(base_scale)
383
+
384
+
385
+ # cropped the clip to the desired video size
386
+ clip = clip.cropped(width=video_size[0], height=video_size[1], x_center=clip.w / 2, y_center=clip.h / 2)
387
+
388
+ # Set the start time of the clip
389
+ clip = clip.with_start(start_time)
390
+
391
+ # Apply random zoom in or zoom out effect with slower zoom
392
+ zoom_type = random.choice(['zoom_in', 'zoom_out', None])
393
+ # if zoom_type == 'zoom_in':
394
+ # # Slow down zoom in effect
395
+ # clip = clip.with_effects([vfx.Resize(lambda t: 1 + zoom_factor * (t / clip.duration))])
396
+ # elif zoom_type == 'zoom_out':
397
+ # # Slow down zoom out effect
398
+ # clip = clip.with_effects([vfx.Resize(lambda t: 1 + zoom_factor * (1 - t / clip.duration))])
399
+
400
+
401
+ # If this is the last clip, apply fadeout
402
+ if i == len(beat_times_full) - 2:
403
+ fade_duration = min(2, clip.duration / 2)
404
+
405
+ clip = clip.with_effects([vfx.FadeOut(fade_duration)])
406
+
407
+ synced_clips.append(clip)
408
+
409
+ # Create the final video
410
+ final_clip = CompositeVideoClip(synced_clips, size=video_size)
411
+
412
+ # Create a list of all clips for final composition
413
+ all_clips = [final_clip]
414
+
415
+ # Add dark overlay if enabled
416
+ if dark_overlay > 0:
417
+ # Create a black ColorClip with the same size as the video
418
+ black_clip = ColorClip(size=video_size, color=(0, 0, 0))
419
+ black_clip = black_clip.with_duration(final_clip.duration)
420
+ black_clip = black_clip.with_opacity(dark_overlay)
421
+ all_clips.append(black_clip)
422
+
423
+ # Add text overlays if provided
424
+ if text_overlays:
425
+ current_time = 0
426
+ for text_item in text_overlays:
427
+ # Handle both string and tuple inputs
428
+ if isinstance(text_item, tuple):
429
+ text, duration = text_item
430
+ else:
431
+ text = text_item
432
+ duration = self._calculate_text_duration(text)
433
+
434
+ # Wrap text if needed
435
+ wrapped_text = self._wrap_text(text)
436
+ print(f"Adding text: '{wrapped_text}' with duration: {duration:.1f}s")
437
+
438
+ # Create text clip with white color and nice font
439
+ txt_clip = TextClip(text=wrapped_text, font_size=40, color='white',
440
+ font='Arial Black',method='label', interline=-1) # 'label' method handles multiline text better
441
+
442
+ # Center the text
443
+ txt_clip = txt_clip.with_position(('center', 'center'))
444
+
445
+ # Set the duration and start time
446
+ txt_clip = txt_clip.with_duration(duration)
447
+ txt_clip = txt_clip.with_start(current_time)
448
+
449
+ # Add fade in/out effects
450
+ txt_clip = txt_clip.with_effects([vfx.FadeIn(0.5), vfx.FadeOut(0.5)])
451
+
452
+ all_clips.append(txt_clip)
453
+ current_time += duration
454
+
455
+ # Create final composition with all layers
456
+ final_clip = CompositeVideoClip(all_clips, size=video_size)
457
+
458
+ # Set the audio of the final clip
459
+ audio_clip = AudioFileClip(self.audio_path).subclipped(0, desired_total_duration)
460
+ # Apply audio fadeout at the end
461
+ fade_duration = min(2, audio_clip.duration / 2)
462
+ audio_clip = audio_clip.with_effects([afx.AudioFadeOut(fade_duration)])
463
+ final_clip = final_clip.with_audio(audio_clip)
464
+ final_clip = final_clip.with_duration(desired_total_duration)
465
+
466
+ # Write the output video file
467
+ final_clip.write_videofile(
468
+ output_path, fps=30, threads=32, audio_codec="aac", preset='ultrafast'
469
+ )
470
+ print(f"Output video saved to '{output_path}'")
tools/beatsyncer/beatsyncer_ui.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+
4
+ import tempfile
5
+ import shutil
6
+
7
+ from tools.beatsyncer.beatsyncer_tool import BeatSyncer
8
+
9
+ class BeatsyncerUI:
10
+ def __init__(self):
11
+ self.temp_dir = tempfile.mkdtemp()
12
+ # Use the existing clips directory from the project
13
+ script_dir = os.path.dirname(os.path.abspath(__file__))
14
+ self.clips_dir = os.path.join(script_dir, 'clips')
15
+
16
+ def process_video(
17
+ self,
18
+ audio_file,
19
+ duration_minutes,
20
+ sensitivity,
21
+ video_height,
22
+ dark_overlay,
23
+ text_input,
24
+ progress=gr.Progress()
25
+ ):
26
+ try:
27
+ # Save audio file
28
+ audio_path = os.path.join(self.temp_dir, "input_audio" + os.path.splitext(audio_file.name)[1])
29
+ shutil.copy(audio_file.name, audio_path)
30
+
31
+ # Process text overlays
32
+ text_overlays = [text.strip() for text in text_input.split('\n') if text.strip()]
33
+
34
+ # Calculate video dimensions
35
+ video_width = int((video_height * 9) / 16) # 16:9 aspect ratio
36
+
37
+ # Set output path
38
+ output_path = os.path.join(self.temp_dir, "output_video.mp4")
39
+
40
+ # Convert duration to float if provided
41
+ if duration_minutes and duration_minutes.strip():
42
+ duration_minutes = float(duration_minutes)
43
+ else:
44
+ duration_minutes = None
45
+
46
+ progress(0.1, desc="Initializing BeatSyncer...")
47
+
48
+ # Initialize BeatSyncer
49
+ beat_syncer = BeatSyncer(audio_path)
50
+
51
+ progress(0.2, desc="Loading audio...")
52
+ beat_syncer.load_audio(duration_minutes=duration_minutes)
53
+
54
+ progress(0.3, desc="Detecting beat drops...")
55
+ beat_syncer.detect_beat_drops(sensitivity=sensitivity)
56
+
57
+ progress(0.4, desc="Creating video...")
58
+ beat_syncer.sync_with_media_directory(
59
+ self.clips_dir, # Using the existing clips directory
60
+ output_path,
61
+ duration_minutes=duration_minutes,
62
+ video_size=(video_width, video_height),
63
+ dark_overlay=float(dark_overlay),
64
+ text_overlays=text_overlays
65
+ )
66
+
67
+ progress(1.0, desc="Done!")
68
+ return output_path
69
+
70
+ except Exception as e:
71
+ raise gr.Error(f"Error: {str(e)}")
72
+
73
+ def create_ui(self):
74
+ with gr.Blocks(theme=gr.themes.Soft()) as app:
75
+ gr.Markdown("""
76
+ # 🎵 BeatSyncer - Video Creation Tool
77
+ Create amazing videos synchronized with music beats!
78
+
79
+ Using media files from: {}
80
+ """.format(self.clips_dir))
81
+
82
+ with gr.Row():
83
+ with gr.Column(scale=2):
84
+ audio_input = gr.File(
85
+ label="Upload Audio/Video File",
86
+ file_types=[".mp3", ".wav", ".m4a", ".aac", ".ogg", ".flac", ".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv"]
87
+ )
88
+
89
+
90
+ duration = gr.Textbox(
91
+ label="Duration (minutes, optional)",
92
+ placeholder="Leave empty for full audio duration"
93
+ )
94
+
95
+ sensitivity = gr.Radio(
96
+ choices=["low", "medium", "high"],
97
+ value="medium",
98
+ label="Beat Detection Sensitivity"
99
+ )
100
+
101
+ video_height = gr.Slider(
102
+ minimum=480,
103
+ maximum=1920,
104
+ value=720,
105
+ step=120,
106
+ label="Video Height (16:9 ratio)"
107
+ )
108
+
109
+ dark_overlay = gr.Slider(
110
+ minimum=0.0,
111
+ maximum=1.0,
112
+ value=0.3,
113
+ step=0.1,
114
+ label="Dark Overlay Intensity"
115
+ )
116
+
117
+ text_input = gr.Textbox(
118
+ label="Text Overlays (one per line)",
119
+ placeholder="Enter text overlays...",
120
+ lines=5
121
+ )
122
+
123
+ create_btn = gr.Button("Create Video", variant="primary")
124
+
125
+ with gr.Column(scale=1):
126
+ output_video = gr.Video(
127
+ label="Output Video",
128
+ height=640, # Fixed height for preview
129
+ width=360 # Fixed width for preview
130
+ )
131
+
132
+ create_btn.click(
133
+ fn=self.process_video,
134
+ inputs=[
135
+ audio_input,
136
+ duration,
137
+ sensitivity,
138
+ video_height,
139
+ dark_overlay,
140
+ text_input
141
+ ],
142
+ outputs=output_video
143
+ )
144
+
145
+ return app
146
+
147
+ # Create and launch the UI
148
+ if __name__ == "__main__":
149
+ ui = BeatSyncerUI()
150
+ demo = ui.create_ui()
151
+ demo.launch(debug=True, share=False, inbrowser=True)
tools/beatsyncer/clips/.DS_Store ADDED
Binary file (16.4 kB). View file
 
tools/beatsyncer/clips/1657591309536.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:69aa9858fca54bda6ac78abe43cf4bd3081ef0d6a69e433603cfe3995219c97c
3
+ size 7222278
tools/beatsyncer/clips/1657591680538.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c531723d9ba697e7453cbc61d772e76957fca7ae83f2ecacaae4b18dd770d7d4
3
+ size 40783923
tools/beatsyncer/clips/1657591832727.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:37be14aab321518e220315f3673dc63113b9a601ae96d246d614b36ea9993a63
3
+ size 19891527
tools/beatsyncer/clips/1657616409629.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:30401ebf2010942c79f16f39dab912ba195c606959bab9500c52dcfa60039aca
3
+ size 10948753
tools/beatsyncer/clips/1657616591950.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:863652f4a1abe5d0d6ee5d0cca92cac3aab6deb7b7d900fa1dfb06289698a274
3
+ size 4494706
tools/beatsyncer/clips/1657616660848.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:09002494bd0c33f02fffecc9ec484da2630dac91399b2ee1adfa22433757b05d
3
+ size 8450597
tools/beatsyncer/clips/1657616730783.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1aad238b7f6623ad10af2413ece4b65113ebae42bb87e41cc39d8276864a7c67
3
+ size 6972420
tools/beatsyncer/clips/1657616788731.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ca25db432389307b40cd11a945d0b19ad0f72e9e7b9d34b2d8e37565e39beb3d
3
+ size 7192248
tools/beatsyncer/clips/1657616954951.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5021eb2d204cdc23fb8e938d0de0d6baa99ac1639726e2b0e3fe2e1e6566a7c3
3
+ size 9827398
tools/beatsyncer/clips/1657923632784.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5837c302c5fe15878b068e22a0ebb1a2fedb747d226ea81a9ba0631fca898540
3
+ size 8853773
tools/beatsyncer/clips/288298605_1687187951639494_579221224540621563_n.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5277a9bc08a20012285e5c438d043047802f525f82583e5c2d4106ec30da8cc3
3
+ size 2767934
tools/beatsyncer/clips/289438954_5129286887125053_220682033208031067_n.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ba5f5469980c5df7e4910122e0cf818bd90fe98e3743650ecf189c706230009c
3
+ size 6625183
tools/beatsyncer/clips/289568281_1462345327513048_3022742540007505592_n.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4801482e63465e85a02fd9a7eacbbe0268954bf9674f55a10aa8c80c9e2753f0
3
+ size 1618834
tools/beatsyncer/clips/290154393_565236208447925_526246679178540543_n.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:671438acc46a10e657388d4c13d34dbecf5af5c6f9ae915b24caec154bf3390a
3
+ size 3465572
tools/beatsyncer/clips/290702607_425330016154178_254797435320646581_n.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:99d8e8e4ee7391d0dc4d3223e5d5d203cea89089fc507862718fdd203c5234f8
3
+ size 3268380
tools/beatsyncer/clips/290990526_409256130973654_5788653198617875871_n.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:191b27a28f64889c201c22daf0bc48ade99d9902b442c3a2d4e8e1ccaebb5060
3
+ size 2297732
tools/beatsyncer/clips/291032176_120067890734229_1814549935092808193_n.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c69c8c738b98305e0b40fbd63ddb6133b886eaf279e8894e232252dad075fc43
3
+ size 3250400
tools/beatsyncer/clips/657591201007.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ef0f3447388144458427572b64c768ec09cc88b87635fbece3192cc2eb973975
3
+ size 11149649
tools/beatsyncer/clips/657591253340.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b1d3c5bd6614ea8e72703a4f94e7590c29a792b9eb0f22504b0f5bb663efbdbb
3
+ size 6907398
tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_001.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9340f70c21ebebfa4230c3891198b278b849a8a3f788bf7f7edd36285f89e830
3
+ size 2103745
tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_002.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b2fff2e4e65479a29a450cdcb340c78564f5794167ee03352dcdc2d8fe856930
3
+ size 155997
tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_003.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:762d0ffc6a657e3c5b1c190190a9b6a3d3ea7a9979239db6de2532658a1dd0b9
3
+ size 350743
tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_004.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0365717355f8e0c8cb39dedbbba0553c1e6132fd2e793972336c2581933eb4d0
3
+ size 563440
tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_005.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bf5ba4edc9c23e961ef5d3379790b0e32b573b314fee099de74b3b14b841a3d1
3
+ size 145942
tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_006.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:174b2c1e98000a026427acde3bdde0914f60ecabfbab8ac93be5dd81a4cf3bc8
3
+ size 282620
tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_007.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f56a247020f35d3ce480f106605fdd704cfddb7513f0b52d7f63646c6e5be4d9
3
+ size 353519
tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_008.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:711df5b60e0ced8b818a49f32e6ca516193346a3b4ce63bb2188d3802f62aace
3
+ size 343334
tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_009.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ab88f7973f2fee383ba852f55d46912aaee7ad849a21a46cdaedb8e957046c6f
3
+ size 204924
tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_010.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5ed2910d6e2e6b4159ba3290053526fa12573082a18dddd38f3d2eebc50ce15f
3
+ size 144843
tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_001.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3b73792168a06223614b33179844a458bd8389f426980414f47ea0389f57cba2
3
+ size 1138637
tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_002.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:85bcdde3e764e65ca3260b20c17918581dfce3acec4eb04e4813a2ac53e16587
3
+ size 566912
tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_003.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:baa62fca4618f79cb7be3b2158509fc97f2f9e7f82991300b6e19b42df217a4d
3
+ size 544288
tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_004.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:102282c000ac3021d2c6749d0164663e936a6e07655d56c1850015fdb148a292
3
+ size 915749