diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..e2aa5b67f3055fef9ae8a5da5aeb388dab795252 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,91 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/1657591309536.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/1657591680538.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/1657591832727.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/1657616409629.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/1657616591950.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/1657616660848.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/1657616730783.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/1657616788731.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/1657616954951.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/1657923632784.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/288298605_1687187951639494_579221224540621563_n.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/289438954_5129286887125053_220682033208031067_n.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/289568281_1462345327513048_3022742540007505592_n.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/290154393_565236208447925_526246679178540543_n.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/290702607_425330016154178_254797435320646581_n.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/290990526_409256130973654_5788653198617875871_n.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/291032176_120067890734229_1814549935092808193_n.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/657591201007.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/657591253340.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_001.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_002.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_003.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_004.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_005.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_006.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_007.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_008.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_009.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_010.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_001.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_002.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_003.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_004.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_005.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_006.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_007.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_001.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_002.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_003.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_004.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_005.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_006.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_6F4E14515715C2DB44E4959237066298_video_dashinit_scene_001.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_6F4E14515715C2DB44E4959237066298_video_dashinit_scene_002.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_7944DC0BB36028CA2D36023098F3029C_video_dashinit.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_001.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_002.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_003.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_004.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_005.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_006.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_007.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_008.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_009.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_010.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_011.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_012.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_013.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_014.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_015.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_016.mp4 filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/ai-generated-8079926_1280.png filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/ai-generated-8601539_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/ai-generated-9151678_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/antique-car-7577214_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/automobile-1850862_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/bmw-6236208_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-171415_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-219813_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-5548243_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-5548244_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-6306695_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-6483720_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-6589240_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-6658749_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-7139944_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-7203265_1280(1).jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-7203265_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-7725834_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-7830737_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/car-8141820_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/fashion-1399346_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/mercedes-benz-7383931_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/speedy-car-8391554_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/sport-4155825_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/vintage-car-7300881_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/clips/vintage-rolls-royce-7414656_1280.jpg filter=lfs diff=lfs merge=lfs -text +tools/beatsyncer/tmp/input_audio.mp3 filter=lfs diff=lfs merge=lfs -text diff --git a/README.md b/README.md index e9f23b0b85f956ee4c3eb17604aa0bae8d8e547c..cd2798f27420c5e57f465ffc33c2ad269d860bb8 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,6 @@ --- -title: AI Tools Platform -emoji: 💻 -colorFrom: blue -colorTo: pink +title: AI_Tools_Platform +app_file: main_ui.py sdk: gradio -sdk_version: 5.48.0 -app_file: app.py -pinned: false +sdk_version: 4.19.2 --- - -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference diff --git a/__pycache__/main_ui.cpython-311.pyc b/__pycache__/main_ui.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ecafb89b82e8e778134b2ed2a9e85a6b65470dbe Binary files /dev/null and b/__pycache__/main_ui.cpython-311.pyc differ diff --git a/colab_launcher.py b/colab_launcher.py new file mode 100644 index 0000000000000000000000000000000000000000..cddc5056bf0f4394234e98104cb6f2dca3a97425 --- /dev/null +++ b/colab_launcher.py @@ -0,0 +1,157 @@ +import os +import sys +import json +import importlib.util +from pathlib import Path + +# Define paths +DRIVE_ROOT = "/content/drive/MyDrive/AI_Tools_Platform" +TOOLS_DIR = os.path.join(DRIVE_ROOT, "tools") +INSTALLED_TOOLS_JSON = os.path.join(DRIVE_ROOT, "installed_tools.json") + +# Import the Tool class from main_ui +from main_ui import Tool, MainUI + +def setup_environment(): + print("Setting up environment...") + + # Create tools directory in Google Drive if it doesn't exist + os.makedirs(TOOLS_DIR, exist_ok=True) + print(f"Tools directory in Drive: {TOOLS_DIR}") + + # Create installed_tools.json in Google Drive if it doesn't exist + if not os.path.exists(INSTALLED_TOOLS_JSON): + with open(INSTALLED_TOOLS_JSON, 'w') as f: + json.dump({"tools": []}, f, indent=4) + print(f"Created initial installed_tools.json in Drive") + else: + print(f"Using existing installed_tools.json from Drive") + + # Verify the content of installed_tools.json + try: + with open(INSTALLED_TOOLS_JSON, 'r') as f: + tools_data = json.load(f) + if not isinstance(tools_data, dict) or "tools" not in tools_data: + print("Warning: installed_tools.json has incorrect format. Fixing...") + with open(INSTALLED_TOOLS_JSON, 'w') as f: + json.dump({"tools": []}, f, indent=4) + except (json.JSONDecodeError, UnicodeDecodeError): + print("Error: installed_tools.json is not valid JSON. Fixing...") + with open(INSTALLED_TOOLS_JSON, 'w') as f: + json.dump({"tools": []}, f, indent=4) + + # Add tools directory to Python path + if TOOLS_DIR not in sys.path: + sys.path.append(TOOLS_DIR) + if os.getcwd() not in sys.path: + sys.path.append(os.getcwd()) + + print("\nEnvironment set up successfully!") + +# Override MainUI methods to use direct file paths +class ColabMainUI(MainUI): + def __init__(self): + # Set up paths before calling parent's __init__ + self.tools = {} # Initialize tools dictionary + self.tools_dir = TOOLS_DIR + self.installed_tools_json = INSTALLED_TOOLS_JSON + self.marketplace_url = "https://marketplace.vidplus.app/api/marketplace" + self.version = "0.1" + + # Create tools directory if it doesn't exist + os.makedirs('tools', exist_ok=True) + + # Load tools manually instead of calling parent's __init__ + self.load_installed_tools() + + def load_installed_tools(self): + """Load installed tools directly from Google Drive""" + print(f"Loading tools from {self.installed_tools_json}") + try: + if os.path.exists(self.installed_tools_json): + with open(self.installed_tools_json, 'r') as f: + data = json.load(f) + + for tool_data in data.get('tools', []): + # If ui_module is specified, dynamically import it + if tool_data.get('ui_module'): + try: + # Modify the module path to use the direct path + module_name = tool_data['ui_module'] + if module_name.startswith('tools.'): + # Extract the relative path and use the full path + rel_path = module_name.replace('tools.', '') + module_path = os.path.join(TOOLS_DIR, rel_path.replace('.', '/') + '.py') + print(f"Loading module from: {module_path}") + + # Use importlib to import the module + spec = importlib.util.spec_from_file_location(module_name, module_path) + if spec: + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + ui_class = getattr(module, tool_data['ui_class']) + + self.tools[tool_data['id']] = Tool( + name=tool_data['name'], + description=tool_data['description'], + ui_class=ui_class, + icon=tool_data['icon'], + version=tool_data['version'], + ui_module=tool_data['ui_module'] + ) + else: + print(f"Could not find module: {module_path}") + else: + # Standard import for non-tools modules + module = importlib.import_module(module_name) + ui_class = getattr(module, tool_data['ui_class']) + + self.tools[tool_data['id']] = Tool( + name=tool_data['name'], + description=tool_data['description'], + ui_class=ui_class, + icon=tool_data['icon'], + version=tool_data['version'], + ui_module=tool_data['ui_module'] + ) + except (ImportError, AttributeError) as e: + print(f"Error loading tool {tool_data['name']}: {e}") + import traceback + traceback.print_exc() + except Exception as e: + print(f"Error loading installed tools: {e}") + import traceback + traceback.print_exc() + + def save_installed_tools(self): + """Save installed tools directly to Google Drive""" + data = { + "tools": [ + { + "id": tool_id, + "name": tool.name, + "description": tool.description, + "icon": tool.icon, + "version": tool.version, + "ui_class": tool.ui_class.__name__, + "ui_module": tool.ui_module + } + for tool_id, tool in self.tools.items() + ] + } + + with open(self.installed_tools_json, 'w') as f: + json.dump(data, f, indent=4) + print(f"Saved tools configuration to {self.installed_tools_json}") + +if __name__ == "__main__": + setup_environment() + + # Import and run the modified UI + print("\nStarting AI Tools Platform...") + + ui = ColabMainUI() + demo = ui.create_ui() + demo.queue() + demo.title = "AI Tools Platform" + demo.launch(debug=True, share=True) # share=True to get a public URL diff --git a/installed_tools.json b/installed_tools.json new file mode 100644 index 0000000000000000000000000000000000000000..3cf71dd2041c18cb70f42a1b201183676600e9e1 --- /dev/null +++ b/installed_tools.json @@ -0,0 +1,40 @@ +{ + "tools": [ + { + "id": "image_background_remover", + "name": "Image Background Remover", + "description": "A tool that removes backgrounds from images", + "icon": "\ud83d\uddbc\ufe0f", + "version": "1.0.0", + "ui_class": "ImageBackgroundRemoverUI", + "ui_module": "tools.image_background_remover.image_background_remover_ui" + }, + { + "id": "beatsyncer", + "name": "Beatsyncer", + "description": "Create amazing videos synchronized with music beats", + "icon": "\ud83c\udfb5", + "version": "1.0.0", + "ui_class": "BeatsyncerUI", + "ui_module": "tools.beatsyncer.beatsyncer_ui" + }, + { + "id": "image_background_remover", + "name": "Image Background Remover", + "description": "A tool that removes backgrounds from images", + "icon": "\ud83d\uddbc\ufe0f", + "version": "1.0.0", + "ui_class": "ImageBackgroundRemoverUI", + "ui_module": "tools.image_background_remover.image_background_remover_ui" + }, + { + "id": "image_background_remover", + "name": "Image Background Remover", + "description": "A tool that removes backgrounds from images", + "icon": "\ud83d\uddbc\ufe0f", + "version": "1.0.0", + "ui_class": "ImageBackgroundRemoverUI", + "ui_module": "tools.image_background_remover.image_background_remover_ui" + } + ] +} \ No newline at end of file diff --git a/main_ui.py b/main_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..66baba61271597861ed7fd952d70734a511d3e6c --- /dev/null +++ b/main_ui.py @@ -0,0 +1,1556 @@ +import os +import json +import importlib +import requests +import gradio as gr +import subprocess + +class Tool: + def __init__(self, name, description, ui_class, icon="🔧", version="0.1", ui_module=None): + self.name = name + self.description = description + self.ui_class = ui_class + self.icon = icon + self.version = version + self.ui_module = ui_module + +class MainUI: + def __init__(self): + # Load installed tools from JSON + self.tools = {} + self.load_installed_tools() + + # Marketplace server URL + self.marketplace_url = "https://marketplace.vidplus.app/api/marketplace" + self.version = "0.1" + + # Create tools directory if it doesn't exist + os.makedirs('tools', exist_ok=True) + + def load_installed_tools(self): + """Load installed tools from JSON file""" + try: + if os.path.exists('installed_tools.json'): + with open('installed_tools.json', 'r') as f: + data = json.load(f) + + for tool_data in data.get('tools', []): + # If ui_module is specified, dynamically import it + if tool_data.get('ui_module'): + try: + module = importlib.import_module(tool_data['ui_module']) + ui_class = getattr(module, tool_data['ui_class']) + + self.tools[tool_data['id']] = Tool( + name=tool_data['name'], + description=tool_data['description'], + ui_class=ui_class, + icon=tool_data['icon'], + version=tool_data['version'], + ui_module=tool_data['ui_module'] + ) + except (ImportError, AttributeError) as e: + print(f"Error loading tool {tool_data['name']}: {e}") + except Exception as e: + print(f"Error loading installed tools: {e}") + # Fallback to default BeatSyncer tool + # self.tools = { + # "beatsyncer": Tool( + # name="BeatSyncer", + # description="Create amazing videos synchronized with music beats", + # ui_class=BeatSyncerUI, + # icon="🎵" + # ) + # } + + def save_installed_tools(self): + """Save installed tools to JSON file""" + data = { + "tools": [ + { + "id": tool_id, + "name": tool.name, + "description": tool.description, + "icon": tool.icon, + "version": tool.version, + "ui_class": tool.ui_class.__name__, + "ui_module": tool.ui_module + } + for tool_id, tool in self.tools.items() + ] + } + + with open('installed_tools.json', 'w') as f: + json.dump(data, f, indent=4) + + def fetch_marketplace_tools(self): + """Fetch available tools from marketplace server""" + try: + response = requests.get(f"{self.marketplace_url}/tools") + if response.status_code == 200: + # Get the list of tools from the marketplace + marketplace_tools = response.json() + + # Ensure we're working with the latest state of installed tools + # This is important after a tool has been removed + if os.path.exists('installed_tools.json'): + with open('installed_tools.json', 'r') as f: + data = json.load(f) + installed_tool_ids = [tool['id'] for tool in data.get('tools', [])] + else: + installed_tool_ids = [] + + # Update the is_installed flag for each tool + for tool in marketplace_tools: + tool['is_installed'] = tool['id'] in installed_tool_ids + + return marketplace_tools + else: + return [] + except Exception as e: + print(f"Error fetching marketplace tools: {e}") + return [] + + def fetch_tool_details(self, tool_id): + """Fetch details for a specific tool from marketplace server""" + try: + response = requests.get(f"{self.marketplace_url}/tools/{tool_id}") + if response.status_code == 200: + return response.json() + else: + return None + except Exception as e: + print(f"Error fetching tool details: {e}") + return None + + def install_tool(self, tool_id): + """Install a tool from the marketplace""" + try: + # Import required modules + import importlib + import importlib.util + import sys + import subprocess + import gradio as gr + + # Fetch tool data + response = requests.get(f"{self.marketplace_url}/tools/{tool_id}/download") + if response.status_code != 200: + return f"Error: Failed to download tool (Status code: {response.status_code})" + + tool_data = response.json() + tool_info = tool_data['tool'] + tool_files = tool_data['files'] + + # Check and install dependencies if they exist + dependencies = tool_info.get('dependencies', []) + if dependencies: + try: + # Read current requirements.txt + current_requirements = [] + if os.path.exists('requirements.txt'): + with open('requirements.txt', 'r') as f: + current_requirements = [line.strip() for line in f.readlines() if line.strip()] + + # Check which dependencies need to be installed + to_install = [] + to_add_to_requirements = [] + + for dep in dependencies: + # Extract just the package name (ignore version requirements) + pkg_name = dep.split('>=')[0].split('==')[0].split('>')[0].split('<')[0].strip() + + # Check if dependency is already in requirements.txt + is_in_requirements = False + for req in current_requirements: + req_name = req.split('>=')[0].split('==')[0].split('>')[0].split('<')[0].strip() + if req_name == pkg_name: + is_in_requirements = True + break + + if not is_in_requirements: + to_add_to_requirements.append(dep) + + try: + # Try to import the package to check if it's installed + importlib.import_module(pkg_name) + except ImportError: + # Package not installed, add to installation list + to_install.append(dep) + + # Install missing dependencies + if to_install: + for dep in to_install: + # Use subprocess to run pip install + subprocess.check_call([sys.executable, "-m", "pip", "install", dep]) + + # Append new dependencies to requirements.txt + if to_add_to_requirements: + with open('requirements.txt', 'a') as f: + for dep in to_add_to_requirements: + f.write(f"\n{dep}") + print(f"Added {len(to_add_to_requirements)} dependencies to requirements.txt") + + except Exception as e: + return f"Error installing dependencies: {e}" + + # Create tool directory + tool_dir = os.path.join('tools', tool_id) + os.makedirs(tool_dir, exist_ok=True) + + # Write tool files + for file_name, file_content in tool_files.items(): + # Rename files to match our convention + target_file_name = file_name + if file_name.endswith('_tool_logic.py'): + target_file_name = f"{tool_id}_tool.py" + elif file_name.endswith('_tool_ui.py'): + target_file_name = f"{tool_id}_ui.py" + + with open(os.path.join(tool_dir, target_file_name), 'w') as f: + f.write(file_content) + + # Create __init__.py to make the directory a package + with open(os.path.join(tool_dir, '__init__.py'), 'w') as f: + f.write(f"# {tool_info['name']} package\n") + + # Add tool to installed tools + ui_module = f"tools.{tool_id}.{tool_id}_ui" + ui_class = f"{tool_id.title().replace('_', '')}UI" + + # Update installed_tools.json + with open('installed_tools.json', 'r') as f: + data = json.load(f) + + data['tools'].append({ + "id": tool_id, + "name": tool_info['name'], + "description": tool_info['description'], + "icon": tool_info['icon'], + "version": tool_info['version'], + "ui_class": ui_class, + "ui_module": ui_module + }) + + with open('installed_tools.json', 'w') as f: + json.dump(data, f, indent=4) + + # Dynamically load the newly installed tool + try: + # Reload the module if it was already imported + if ui_module in sys.modules: + importlib.reload(sys.modules[ui_module]) + + # Import the module + module = importlib.import_module(ui_module) + ui_class_obj = getattr(module, ui_class) + + # Add the tool to self.tools + self.tools[tool_id] = Tool( + name=tool_info['name'], + description=tool_info['description'], + ui_class=ui_class_obj, + icon=tool_info['icon'], + version=tool_info['version'], + ui_module=ui_module + ) + + # Flag to indicate this is a newly installed tool that needs special handling + self.tools[tool_id].newly_installed = True + + # Return success message + return f"Successfully installed {tool_info['name']}. The tool is now available in the Tools page." + except Exception as e: + print(f"Error dynamically loading tool: {e}") + import traceback + traceback.print_exc() + # Still return success but with a restart message + return f"Successfully installed {tool_info['name']}. Please restart the application to use the new tool." + except Exception as e: + return f"Error installing tool: {e}" + + def remove_tool(self, tool_id): + """Remove an installed tool""" + try: + if tool_id not in self.tools: + return "Error: Tool is not installed" + + # Get tool info before removal + tool_name = self.tools[tool_id].name + + # Remove tool directory + tool_dir = os.path.join('tools', tool_id) + if os.path.exists(tool_dir): + import shutil + shutil.rmtree(tool_dir) + print(f"Removed tool directory: {tool_dir}") + + # Remove tool from installed_tools.json + if os.path.exists('installed_tools.json'): + with open('installed_tools.json', 'r') as f: + data = json.load(f) + + # Filter out the tool to be removed + data['tools'] = [tool for tool in data['tools'] if tool['id'] != tool_id] + + # Save updated tools list + with open('installed_tools.json', 'w') as f: + json.dump(data, f, indent=4) + + print(f"Removed tool from installed_tools.json: {tool_id}") + + # Remove tool from self.tools + del self.tools[tool_id] + + return f"✅ Successfully removed {tool_name}" + except Exception as e: + print(f"Error removing tool: {e}") + import traceback + traceback.print_exc() + return f"Error removing tool: {str(e)}" + + def create_tool_card(self, name): + return f""" +
+ {name} +
+ """ + + def create_ui(self): + css = """ + :root { + --sidebar-width: 200px; + } + + body { + margin: 0; + padding: 0; + overflow: hidden; + } + + #sidebar { + background-color: #0A192F; + color: white; + padding: 32px 24px; + height: 100vh; + width: var(--sidebar-width); + position: fixed; + left: 0; + top: 0; + display: flex; + flex-direction: column; + box-sizing: border-box; + z-index: 1000; + } + + #main-content { + background-color: #0F172A; + min-height: 100vh; + margin-left: var(--sidebar-width); + padding: 50px; + box-sizing: border-box; + overflow-y: auto; + position: relative; + max-height: 100vh; /* Ensure content is scrollable */ + } + + .nav-button { + background: none !important; + border: none !important; + box-shadow: none !important; + color: #94A3B8 !important; + padding: 8px 0 !important; + margin: 4px 0 !important; + min-width: unset !important; + width: 100% !important; + text-align: left !important; + font-size: 16px !important; + font-weight: 500 !important; + height: auto !important; + line-height: 1.2 !important; + } + + .nav-button:hover { + color: white !important; + } + + .nav-button[variant="primary"] { + color: white !important; + } + + .tool-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 20px; + padding: 24px 0; + width: 100%; + } + + .version-text { + margin-top: auto; + color: #64748B; + font-size: 12px; + padding-top: 16px; + } + + .title { + font-size: 32px; + font-weight: 800; + margin-bottom: 32px; + color: white; + letter-spacing: -0.5px; + } + + .page-title { + font-size: 28px; + font-weight: 700; + color: white; + margin: 0; + padding: 0; + letter-spacing: -0.5px; + } + + #content-wrapper { + width: 100%; + max-width: 100%; + padding: 24px; + } + + .tool-card { + background: #1E293B !important; + border: none !important; + border-radius: 16px !important; + overflow: hidden !important; + transition: transform 0.2s !important; + box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important; + height: 100% !important; + display: flex !important; + flex-direction: column !important; + width: auto !important; + min-height: 200px !important; + justify-content: flex-start !important; + align-items: stretch !important; + padding: 0 !important; + color: white !important; + text-align: left !important; + cursor: pointer !important; + } + + .tool-card:hover { + transform: scale(1.02) !important; + } + + .tool-card-img { + width: 100%; + height: 150px; + background: #2D3748; + display: flex; + align-items: center; + justify-content: center; + font-size: 48px; + color: #38BDF8; + } + + .tool-card-content { + padding: 16px !important; + flex-grow: 1 !important; + display: flex !important; + flex-direction: column !important; + } + + .tool-card-title { + font-size: 18px !important; + font-weight: 600 !important; + color: white !important; + margin: 0 0 8px 0 !important; + } + + .tool-card-description { + font-size: 14px !important; + color: #94A3B8 !important; + margin: 0 !important; + flex-grow: 1 !important; + } + + .installed-indicator { + background-color: #10B981 !important; + color: white !important; + font-size: 12px !important; + font-weight: 600 !important; + padding: 4px 8px !important; + border-radius: 4px !important; + margin-top: 8px !important; + display: inline-block !important; + } + + .tool-card.installed { + border: 2px solid #10B981 !important; + } + + .add-tool-card { + background: rgba(255, 255, 255, 0.05) !important; + border: 2px dashed rgba(255, 255, 255, 0.2) !important; + color: rgba(255, 255, 255, 0.8) !important; + display: flex !important; + flex-direction: column !important; + justify-content: center !important; + align-items: center !important; + text-align: center !important; + padding: 16px !important; + } + + .add-tool-icon { + font-size: 32px; + margin-bottom: 16px; + color: rgba(255, 255, 255, 0.5); + } + + .tool-card-button { + background: transparent !important; + border: none !important; + padding: 0 !important; + margin: 0 !important; + width: 100% !important; + height: auto !important; + min-height: 0 !important; + box-shadow: none !important; + } + + .tool-details { + width: 100%; + max-width: 100%; + padding: 0; + } + + .tool-details-container { + background: #0F172A; + border-radius: 0; + padding: 32px; + margin-bottom: 0; + max-height: 100vh; + overflow-y: auto; + width: 100%; + } + + .tool-details-header { + display: flex; + align-items: center; + margin-bottom: 24px; + justify-content: space-between; + } + + .tool-details-title-section { + display: flex; + align-items: center; + } + + .tool-details-icon { + font-size: 28px; + margin-right: 16px; + } + + .tool-details-title { + font-size: 28px; + font-weight: 700; + color: white; + margin: 0; + } + + .tool-details-version { + font-size: 14px; + color: #94A3B8; + margin-left: 8px; + } + + .tool-details-description { + font-size: 16px; + color: #E2E8F0; + margin-bottom: 32px; + max-width: 800px; + } + + .tool-details-section-title { + font-size: 18px; + font-weight: 600; + color: white; + margin: 32px 0 16px 0; + } + + .tool-details-features { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: 32px; + } + + .tool-details-feature { + display: flex; + align-items: center; + margin-bottom: 0; + } + + .tool-details-feature-icon { + color: #38BDF8; + margin-right: 12px; + font-size: 16px; + } + + .tool-details-feature-text { + color: #E2E8F0; + font-size: 16px; + } + + .tool-details-screenshots { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 16px; + margin-top: 16px; + } + + .tool-details-screenshot { + width: 100%; + border-radius: 8px; + overflow: hidden; + } + + .tool-details-video { + width: 100%; + aspect-ratio: 16/9; + border-radius: 8px; + overflow: hidden; + margin-top: 16px; + background: #1E293B; + border: none; + max-width: 800px; + height: 450px; + } + + .back-button { + background: transparent !important; + border: none !important; + color: #94A3B8 !important; + font-size: 14px !important; + padding: 8px 16px !important; + border-radius: 8px !important; + cursor: pointer !important; + transition: background-color 0.2s !important; + text-align: left !important; + width: auto !important; + margin-bottom: 24px !important; + } + + .back-button:hover { + background: rgba(255, 255, 255, 0.1) !important; + } + + .install-button { + background: #38BDF8 !important; + color: #0F172A !important; + font-weight: 600 !important; + padding: 12px 24px !important; + border-radius: 8px !important; + margin-top: 24px !important; + width: 100% !important; + } + + .install-button:hover { + background: #0EA5E9 !important; + } + + .remove-button { + background: #EF4444 !important; + color: white !important; + font-weight: 600 !important; + padding: 12px 24px !important; + border-radius: 8px !important; + margin-top: 24px !important; + width: 100% !important; + border: none !important; + cursor: pointer !important; + transition: background-color 0.2s !important; + } + + .remove-button:hover { + background: #DC2626 !important; + } + + .tool-details-action .remove-button { + margin-top: 0 !important; + padding: 8px 16px !important; + font-size: 14px !important; + width: auto !important; + } + + /* Confirmation dialog styling */ + .confirmation-dialog { + background-color: #1E293B; + border-radius: 8px; + padding: 24px; + margin-bottom: 24px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + border: 1px solid #2D3748; + } + + .confirmation-title { + font-size: 18px; + font-weight: 600; + color: white; + margin-bottom: 16px; + } + + .confirmation-message { + font-size: 14px; + color: #E2E8F0; + margin-bottom: 24px; + } + + .confirmation-buttons { + display: flex; + justify-content: flex-end; + gap: 12px; + } + + .cancel-button { + background: #4B5563 !important; + color: white !important; + font-weight: 600 !important; + padding: 8px 16px !important; + border-radius: 8px !important; + border: none !important; + cursor: pointer !important; + transition: background-color 0.2s !important; + } + + .cancel-button:hover { + background: #374151 !important; + } + + .confirm-button { + background: #EF4444 !important; + color: white !important; + font-weight: 600 !important; + padding: 8px 16px !important; + border-radius: 8px !important; + border: none !important; + cursor: pointer !important; + transition: background-color 0.2s !important; + } + + .confirm-button:hover { + background: #DC2626 !important; + } + + /* Modal styling */ + #installation-modal { + max-width: 500px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + } + + #installation-status { + font-size: 18px; + font-weight: 600; + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid #e5e7eb; + } + + #installation-result { + font-size: 14px; + margin-bottom: 20px; + white-space: pre-wrap; + max-height: 300px; + overflow-y: auto; + } + + #close-modal-btn { + background-color: #6B7280; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-weight: 500; + transition: background-color 0.2s; + margin-top: 12px; + } + + #close-modal-btn:hover { + background-color: #4B5563; + } + + /* Installation result styling */ + #installation-result-container { + margin-bottom: 20px; + border: 1px solid #2D3748; + border-radius: 8px; + padding: 16px; + background-color: #1E293B; + max-width: 100%; + position: relative; + z-index: 100; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + margin-top: 0; + } + + #installation-status { + font-size: 18px; + font-weight: 600; + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid #2D3748; + } + + #installation-result { + font-size: 14px; + margin-bottom: 20px; + white-space: pre-wrap; + max-height: 300px; + overflow-y: auto; + } + + #close-result-btn { + background-color: #6B7280; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-weight: 500; + transition: background-color 0.2s; + margin-top: 12px; + } + + #close-result-btn:hover { + background-color: #4B5563; + } + + .tool-details-action { + display: flex; + align-items: center; + } + + .tool-details-action .install-button { + margin-top: 0 !important; + padding: 8px 16px !important; + font-size: 14px !important; + width: auto !important; + } + """ + + with gr.Blocks(theme=gr.themes.Default(), css=css) as app: + # Define state variables + marketplace_tools_state = gr.State([]) + current_tool_details_state = gr.State(None) + + # Define event handlers first, before any references to them + def show_tools(): + # Create updates to hide all tool containers + tool_container_updates = [gr.update(visible=False) for _ in tool_containers] + + # Generate fresh HTML for tools grid based on current tools + tools_html = '' + for tool_id, tool in self.tools.items(): + # Create HTML for each tool card + tools_html += f''' +
+
+ {tool.icon} +
+
+

{tool.name}

+

{tool.description if hasattr(tool, 'description') else ""}

+
+
+ ''' + + # Add "Add New Tool" button + tools_html += ''' +
+
+
+
Add New Tool from Marketplace
+
+ ''' + + # Create HTML update for tools grid + tools_html_component = gr.HTML(f'
{tools_html}
') + + return [ + gr.update(visible=True), # tools_content + gr.update(visible=False), # marketplace_content + gr.update(visible=False), # settings_content + gr.update(visible=False), # tool_interfaces + gr.update(visible=False), # tool_details_content + gr.update(variant="primary"), # tools_btn + gr.update(variant="secondary"), # marketplace_btn + gr.update(variant="secondary"), # settings_btn, + tools_html_component, # tools_html_component + gr.update(value="") # tool_interfaces_html + ] + tool_container_updates + + def show_marketplace(): + # Fetch marketplace tools + tools = self.fetch_marketplace_tools() + + # Create HTML for marketplace grid + html = '
' + + for i, tool in enumerate(tools): + # Use a unique ID for each card based on the tool ID + # Add a class to indicate if the tool is installed + installed_class = "installed" if tool.get('is_installed', False) else "" + + # Add an indicator for installed tools + installed_indicator = '
✓ Installed
' if tool.get('is_installed', False) else '' + + html += f''' +
+
+ {tool['icon']} +
+
+

{tool['name']}

+

{tool['description']}

+ {installed_indicator} +
+
+ ''' + + html += '
' + + # Hide installation result container and confirmation dialog + installation_result_container_update = gr.update(visible=False) + confirmation_dialog_update = gr.update(visible=False) + + # Create updates for all tool containers + tool_container_updates = [gr.update(visible=False) for _ in tool_containers] + + # Update marketplace_tools_state with the latest tools + # This ensures the marketplace shows the current state of available tools + + return [ + gr.update(visible=False), # tools_content + gr.update(visible=True), # marketplace_content + gr.update(visible=False), # settings_content + gr.update(visible=False), # tool_interfaces + gr.update(visible=False), # tool_details_content + gr.update(variant="secondary"), # tools_btn + gr.update(variant="primary"), # marketplace_btn + gr.update(variant="secondary"), # settings_btn + tools, # marketplace_tools_state - updated with latest tools + html, # marketplace_html + installation_result_container_update, # installation_result_container + confirmation_dialog_update, # confirmation_dialog + gr.update(value="") # tool_interfaces_html + ] + tool_container_updates + + def show_settings(): + # Create updates to hide all tool containers + tool_container_updates = [gr.update(visible=False) for _ in tool_containers] + + return [ + gr.update(visible=False), # tools_content + gr.update(visible=False), # marketplace_content + gr.update(visible=True), # settings_content + gr.update(visible=False), # tool_interfaces + gr.update(visible=False), # tool_details_content + gr.update(variant="secondary"), # tools_btn + gr.update(variant="secondary"), # marketplace_btn + gr.update(variant="primary"), # settings_btn + gr.update(value="") # tool_interfaces_html + ] + tool_container_updates + + def show_tool(tool_id=None): + # Import gradio at the beginning of the function + import gradio as gr + + # Check if the tool exists + if tool_id not in self.tools: + print(f"Tool {tool_id} not found") + return show_tools() + + # Get the tool + tool = self.tools[tool_id] + + # Check if this is a newly installed tool that needs special handling + if hasattr(tool, 'newly_installed') and tool.newly_installed: + print(f"Dynamically creating UI for newly installed tool: {tool_id}") + + try: + # Create a message to inform the user that we're loading the tool + loading_message = f""" +
+

Loading {tool.name}...

+

Please wait while we set up the tool interface.

+
+ """ + + # First, return a loading message + loading_updates = [ + gr.update(visible=False), # tools_content + gr.update(visible=False), # marketplace_content + gr.update(visible=False), # settings_content + gr.update(visible=True), # tool_interfaces + gr.update(visible=False), # tool_details_content + gr.update(variant="secondary"), # tools_btn + gr.update(variant="secondary"), # marketplace_btn + gr.update(variant="secondary"), # settings_btn + gr.update(value=loading_message) # tool_interfaces_html + ] + [gr.update(visible=False) for _ in tool_containers] + + # Instead of trying to create the UI dynamically, which can cause issues with Gradio components, + # we'll create a simple HTML interface that provides basic functionality and instructions + + # Get the tool class name and module for display + tool_class_name = tool.ui_class.__name__ + tool_module_name = tool.ui_module + + # Create a friendly HTML interface + tool_html = f""" +
+

{tool.name} is Ready!

+

This tool has been successfully installed.

+

For the best experience with this tool, please restart the application.

+

After restarting, you'll be able to use all the features of this tool.

+ +
+ """ + + # Remove the newly_installed flag so we don't try to create the UI again + delattr(tool, 'newly_installed') + + # Return the HTML interface + return [ + gr.update(visible=False), # tools_content + gr.update(visible=False), # marketplace_content + gr.update(visible=False), # settings_content + gr.update(visible=True), # tool_interfaces + gr.update(visible=False), # tool_details_content + gr.update(variant="secondary"), # tools_btn + gr.update(variant="secondary"), # marketplace_btn + gr.update(variant="secondary"), # settings_btn + gr.update(value=tool_html) # tool_interfaces_html + ] + [gr.update(visible=False) for _ in tool_containers] + except Exception as e: + print(f"Error creating UI for newly installed tool: {e}") + import traceback + traceback.print_exc() + + # If there's an error, show a message asking the user to restart + restart_message = f""" +
+

Error Loading {tool.name}

+

There was an error setting up the tool interface:

+
{str(e)}
+

Please restart the application to use this tool.

+
+ +
+
+ """ + + return [ + gr.update(visible=False), # tools_content + gr.update(visible=False), # marketplace_content + gr.update(visible=False), # settings_content + gr.update(visible=True), # tool_interfaces + gr.update(visible=False), # tool_details_content + gr.update(variant="secondary"), # tools_btn + gr.update(variant="secondary"), # marketplace_btn + gr.update(variant="secondary"), # settings_btn + gr.update(value=restart_message) # tool_interfaces_html + ] + [gr.update(visible=False) for _ in tool_containers] + + # For existing tools with proper UI containers + # Create a list of visibility updates for all tool containers + updates = [] + + for i, container in enumerate(tool_containers): + # Get the tool_id for this container + container_tool_id = None + for tid, t in self.tools.items(): + if hasattr(t, 'ui_container') and t.ui_container == container: + container_tool_id = tid + break + + # Set visibility based on whether this is the selected tool + if container_tool_id == tool_id: + updates.append(gr.update(visible=True)) + else: + updates.append(gr.update(visible=False)) + + return [ + gr.update(visible=False), # tools_content + gr.update(visible=False), # marketplace_content + gr.update(visible=False), # settings_content + gr.update(visible=True), # tool_interfaces + gr.update(visible=False), # tool_details_content + gr.update(variant="secondary"), # tools_btn + gr.update(variant="secondary"), # marketplace_btn + gr.update(variant="secondary"), # settings_btn + gr.update(value="") # tool_interfaces_html + ] + updates + + def show_tool_details(tool_id): + # Fetch tool details + tool = self.fetch_tool_details(tool_id) + + # Create updates to hide all tool containers + tool_container_updates = [gr.update(visible=False) for _ in tool_containers] + + if not tool: + return [ + gr.update(visible=False), # tools_content + gr.update(visible=True), # marketplace_content + gr.update(visible=False), # settings_content + gr.update(visible=False), # tool_interfaces + gr.update(visible=False), # tool_details_content + gr.update(variant="secondary"), # tools_btn + gr.update(variant="primary"), # marketplace_btn + gr.update(variant="secondary"), # settings_btn + None, # current_tool_details_state + "", # tool_details_html + gr.update(visible=False), # installation_result_container + gr.update(visible=False), # confirmation_dialog + gr.update(value="") # tool_interfaces_html + ] + tool_container_updates + + # Create features HTML + features_html = "".join([ + f'
{feature}
' + for feature in tool.get('features', []) + ]) + + # Create demo video HTML + demo_video_html = f'' if True else '' + + # Check if the tool is already installed + # Use the is_installed flag if available, otherwise check self.tools + is_installed = tool.get('is_installed', False) or tool_id in self.tools + + # Determine which button to show based on installation status + if is_installed: + action_button = f'' + else: + action_button = f'' + + # Create HTML for tool details + html = f''' +
+
+
+
+
{tool['icon']}
+

{tool['name']} v{tool['version']}

+
+
+ {action_button} +
+
+

{tool['description']}

+ +

Features

+
+ {features_html} +
+ +

Video

+ {demo_video_html} +
+
+ ''' + + print(f"Setting current_tool_details_state to: {tool}") + return [ + gr.update(visible=False), # tools_content + gr.update(visible=False), # marketplace_content + gr.update(visible=False), # settings_content + gr.update(visible=False), # tool_interfaces + gr.update(visible=True), # tool_details_content + gr.update(variant="secondary"), # tools_btn + gr.update(variant="secondary"), # marketplace_btn + gr.update(variant="secondary"), # settings_btn + tool, # current_tool_details_state + html, # tool_details_html + gr.update(visible=False), # installation_result_container + gr.update(visible=False), # confirmation_dialog + gr.update(value="") # tool_interfaces_html + ] + tool_container_updates + + def install_selected_tool(tool_details): + print(f"Installing tool: {tool_details}") + + if not tool_details: + print("No tool details provided") + return [ + gr.update(visible=True), # installation_result_container + "⚠️ Installation Failed", # installation_status + "No tool selected to install." # installation_result + ] + + try: + # Install the tool + result = self.install_tool(tool_details['id']) + + if "Error" in result: + return [ + gr.update(visible=True), # installation_result_container + "⚠️ Installation Failed", # installation_status + result # installation_result + ] + else: + # If installation was successful, we'll show a success message + # The user can click "Close" to see the installation result + # and then navigate to the tools page to see the new tool + return [ + gr.update(visible=True), # installation_result_container + "✅ Installation Successful", # installation_status + result # installation_result + ] + except Exception as e: + print(f"Error installing tool: {e}") + return [ + gr.update(visible=True), # installation_result_container + "⚠️ Installation Failed", # installation_status + f"Error installing tool: {e}" # installation_result + ] + + # Create components after defining the functions + with gr.Row(): + # Sidebar + with gr.Column(elem_id="sidebar", scale=1): + gr.Markdown("AI TOOLS", elem_classes="title") + tools_btn = gr.Button("Tools", elem_classes="nav-button", variant="primary") + marketplace_btn = gr.Button("Marketplace", elem_classes="nav-button", variant="secondary") + settings_btn = gr.Button("Settings", elem_classes="nav-button", variant="secondary") + gr.Markdown(f"version: {self.version}", elem_classes="version-text") + + # Main content + with gr.Column(elem_id="main-content", scale=4): + # Define all content containers + tools_content = gr.Column(visible=True) + marketplace_content = gr.Column(visible=False) + settings_content = gr.Column(visible=False) + tool_interfaces = gr.Column(visible=False) + tool_details_content = gr.Column(visible=False) + + # Fill tools content + with tools_content: + gr.Markdown("Tools", elem_classes="page-title") + + tool_buttons = [] + tool_ids = [] # Store tool_ids in the same order as tool_buttons + + # Create HTML for all tools + tools_html = '' + + for tool_id, tool in self.tools.items(): + # Create HTML for each tool card + tools_html += f''' +
+
+ {tool.icon} +
+
+

{tool.name}

+

{tool.description if hasattr(tool, 'description') else ""}

+
+
+ ''' + + # Create hidden buttons for tool selection + btn = gr.Button(f"Select {tool.name}", visible=False, elem_id=f"tool-btn-{tool_id}") + tool_buttons.append(btn) + tool_ids.append(tool_id) + + # Add "Add New Tool" button + tools_html += ''' +
+
+
+
Add New Tool from Marketplace
+
+ ''' + + # Render the HTML inside a div with the tool-grid class + tools_html_component = gr.HTML(f'
{tools_html}
') + + # Create hidden add tool button + add_tool_btn = gr.Button("Add New Tool", visible=False, elem_id="add-tool-btn") + + # Fill marketplace content + with marketplace_content: + gr.Markdown("Marketplace", elem_classes="page-title") + marketplace_html = gr.HTML("Loading marketplace tools...") + + # Create hidden buttons for marketplace tool details with unique IDs + tool_buttons_container = gr.Column(visible=False) + + # Create hidden buttons for marketplace tools + marketplace_tool_buttons = [] + for i, tool in enumerate(self.fetch_marketplace_tools()): + tool_btn = gr.Button(f"View {tool['name']}", visible=False, elem_id=f"marketplace-tool-btn-{i}") + marketplace_tool_buttons.append((tool['id'], tool_btn)) + + # The click handlers for marketplace tool buttons will be set up after all components are defined + + # Fill tool details content + with tool_details_content: + # Installation result section at the top + with gr.Column(elem_id="installation-result-container", elem_classes="installation-result-container", visible=False) as installation_result_container: + installation_status = gr.Markdown("Ready to install", elem_id="installation-status") + installation_result = gr.Markdown("Click the Install Tool button to install the selected tool.", elem_id="installation-result") + close_result_btn = gr.Button("Close", elem_id="close-result-btn") + + # Confirmation dialog for tool removal + with gr.Column(elem_id="confirmation-dialog", elem_classes="confirmation-dialog", visible=False) as confirmation_dialog: + gr.Markdown("⚠️ Warning: Remove Tool", elem_id="confirmation-title", elem_classes="confirmation-title") + gr.Markdown( + "Are you sure you want to remove this tool? This action cannot be undone. " + "All data created by this tool will be permanently deleted.", + elem_id="confirmation-message", + elem_classes="confirmation-message" + ) + with gr.Row(elem_classes="confirmation-buttons"): + cancel_btn = gr.Button("Cancel", elem_classes="cancel-button") + confirm_btn = gr.Button("Yes, Remove Tool", elem_classes="confirm-button") + + with gr.Row(): + with gr.Column(scale=1): + back_btn = gr.Button("← Back to Marketplace", elem_classes="back-button") + with gr.Column(scale=3): + gr.Markdown("", elem_classes="page-title") + + # Tool details content + tool_details_html = gr.HTML("") + + # Hidden install button (will be triggered by the inline button) + install_btn = gr.Button("Add Tool", elem_classes="install-button", elem_id="install-btn", variant="primary", visible=False) + + # Hidden remove button (will be triggered by the inline button) + remove_btn = gr.Button("Remove Tool", elem_classes="remove-button", elem_id="remove-btn", variant="primary", visible=False) + + # Add a direct event handler to the install button + def debug_install_click(): + print("Install button clicked directly!") + return [gr.update(visible=True), gr.update(value="Install button clicked!")] + + # Add a direct event handler to the remove button to show confirmation dialog + def show_confirmation_dialog(): + print("Remove button clicked - showing confirmation dialog") + return gr.update(visible=True) + + # Function to handle tool removal confirmation + def remove_selected_tool(tool_details): + print(f"Removing tool: {tool_details}") + + if not tool_details: + print("No tool details provided") + return [ + gr.update(visible=False), # confirmation_dialog + gr.update(visible=True), # installation_result_container + "⚠️ Removal Failed", # installation_status + "No tool selected to remove." # installation_result + ] + + try: + # Remove the tool + result = self.remove_tool(tool_details['id']) + + if "Error" in result: + return [ + gr.update(visible=False), # confirmation_dialog + gr.update(visible=True), # installation_result_container + "⚠️ Removal Failed", # installation_status + result # installation_result + ] + else: + # After successful removal, show success message + # The user will need to click "Back to Marketplace" manually + # This avoids issues with dynamic UI updates + return [ + gr.update(visible=False), # confirmation_dialog + gr.update(visible=True), # installation_result_container + "✅ Removal Successful", # installation_status + f"{result} - Click 'Back to Marketplace' to return. You may need to restart the application to see all changes." # installation_result + ] + except Exception as e: + print(f"Error removing tool: {e}") + return [ + gr.update(visible=False), # confirmation_dialog + gr.update(visible=True), # installation_result_container + "⚠️ Removal Failed", # installation_status + f"Error removing tool: {e}" # installation_result + ] + + # Function to cancel removal + def cancel_removal(): + return gr.update(visible=False) + + debug_output = gr.Textbox(label="Debug Output", visible=False) + + # Set up event handlers for buttons + install_btn.click( + fn=install_selected_tool, + inputs=[current_tool_details_state], + outputs=[installation_result_container, installation_status, installation_result] + ) + remove_btn.click(fn=show_confirmation_dialog, outputs=confirmation_dialog) + + # Set up confirmation dialog buttons + cancel_btn.click(fn=cancel_removal, outputs=confirmation_dialog) + confirm_btn.click( + fn=remove_selected_tool, + inputs=[current_tool_details_state], + outputs=[confirmation_dialog, installation_result_container, installation_status, installation_result] + ) + + # Fill settings content + with settings_content: + gr.Markdown("Settings", elem_classes="page-title") + gr.Markdown("Settings options will appear here...") + + # Fill tool interfaces + with tool_interfaces: + # Add an HTML component to display messages for newly installed tools + tool_interfaces_html = gr.HTML("", elem_id="tool-interfaces-html") + + for tool_id, tool in self.tools.items(): + with gr.Column(visible=False) as tool_container: + tool_ui = tool.ui_class() + tool_ui.create_ui() + # Store the UI container in the tool object for later reference + tool.ui_container = tool_container + + # Set up event handlers after all components are defined + # Set up tool button events + + # Collect all tool containers for outputs + tool_containers = [] + for tool_id, tool in self.tools.items(): + if hasattr(tool, 'ui_container'): + tool_containers.append(tool.ui_container) + + # Set up click handlers for marketplace tool buttons + for i, (tool_id, tool_btn) in enumerate(marketplace_tool_buttons): + # We need to create a separate function for each button to avoid lambda closure issues + def make_handler(tid): + return lambda: show_tool_details(tid) + + handler = make_handler(tool_id) + tool_btn.click( + fn=handler, + outputs=[ + tools_content, marketplace_content, settings_content, tool_interfaces, + tool_details_content, tools_btn, marketplace_btn, settings_btn, + current_tool_details_state, tool_details_html, installation_result_container, + confirmation_dialog, tool_interfaces_html + ] + tool_containers + ) + + for i, btn in enumerate(tool_buttons): + if isinstance(btn, gr.Button): # Check if it's a button from the tools grid + tool_id = tool_ids[i] # Get the corresponding tool_id + + # We need to create a separate function for each button to avoid lambda closure issues + def make_handler(tid): + return lambda: show_tool(tid) + + handler = make_handler(tool_id) + btn.click( + fn=handler, + outputs=[tools_content, marketplace_content, settings_content, tool_interfaces, + tool_details_content, tools_btn, marketplace_btn, settings_btn, + tool_interfaces_html] + tool_containers + ) + + # Set up navigation events + tools_btn.click( + fn=show_tools, + outputs=[tools_content, marketplace_content, settings_content, tool_interfaces, + tool_details_content, tools_btn, marketplace_btn, settings_btn, + tools_html_component, tool_interfaces_html] + tool_containers + ) + + marketplace_btn.click( + fn=show_marketplace, + outputs=[tools_content, marketplace_content, settings_content, tool_interfaces, + tool_details_content, tools_btn, marketplace_btn, settings_btn, + marketplace_tools_state, marketplace_html, installation_result_container, + confirmation_dialog, tool_interfaces_html] + tool_containers + ) + + settings_btn.click( + fn=show_settings, + outputs=[tools_content, marketplace_content, settings_content, tool_interfaces, + tool_details_content, tools_btn, marketplace_btn, settings_btn, + tool_interfaces_html] + tool_containers + ) + + # Set up add tool button to navigate to marketplace + add_tool_btn.click( + fn=show_marketplace, + outputs=[tools_content, marketplace_content, settings_content, tool_interfaces, + tool_details_content, tools_btn, marketplace_btn, settings_btn, + marketplace_tools_state, marketplace_html, installation_result_container, + confirmation_dialog, tool_interfaces_html] + tool_containers + ) + + # Set up event handlers for tool details + back_btn.click( + fn=show_marketplace, + outputs=[ + tools_content, marketplace_content, settings_content, tool_interfaces, + tool_details_content, tools_btn, marketplace_btn, settings_btn, + marketplace_tools_state, marketplace_html, installation_result_container, + confirmation_dialog + ] + tool_containers + ) + + # Define a function to handle closing the installation result and navigating to tools + def close_result_and_show_tools(): + # First hide the installation result container + installation_result_update = gr.update(visible=False) + + # Then get the updates from show_tools + tools_updates = show_tools() + + # Combine the updates + return [installation_result_update] + tools_updates + + # Set up close button for installation result + close_result_btn.click( + fn=close_result_and_show_tools, + outputs=[installation_result_container, + tools_content, marketplace_content, settings_content, tool_interfaces, + tool_details_content, tools_btn, marketplace_btn, settings_btn, + tools_html_component, tool_interfaces_html] + tool_containers + ) + + return app + +if __name__ == "__main__": + ui = MainUI() + demo = ui.create_ui() + demo.queue() # Add queue for better handling of multiple requests + demo.title = "AI Tools Platform" # Add a title + demo.launch( + debug=True, + share=True, + server_name="0.0.0.0", + server_port=7860 + ) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..bfc77761499867c67cb18cf780decea68500a78d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +librosa==0.10.1 +numpy==1.26.3 +moviepy==2.1.2 +opencv-python>=4.8.0 +scikit-learn>=1.3.0 +scenedetect~=0.6.5.2 +gradio==4.19.2 +flask==2.3.3 +requests==2.31.0 +rembg +Pillow +onnxruntime \ No newline at end of file diff --git a/tools/beatsyncer/__init__.py b/tools/beatsyncer/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8c5ffaa47c0c5aabb128a607463b25ff6df132c0 --- /dev/null +++ b/tools/beatsyncer/__init__.py @@ -0,0 +1 @@ +# Beatsyncer package diff --git a/tools/beatsyncer/__pycache__/__init__.cpython-311.pyc b/tools/beatsyncer/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0455f85a42b0b6394c8fcdb3299ef1a36a46d536 Binary files /dev/null and b/tools/beatsyncer/__pycache__/__init__.cpython-311.pyc differ diff --git a/tools/beatsyncer/__pycache__/__init__.cpython-312.pyc b/tools/beatsyncer/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe1710051c724ce0887295870d0cbbca8ead476a Binary files /dev/null and b/tools/beatsyncer/__pycache__/__init__.cpython-312.pyc differ diff --git a/tools/beatsyncer/__pycache__/beatsyncer_tool.cpython-311.pyc b/tools/beatsyncer/__pycache__/beatsyncer_tool.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3586b6413d824cedc919a0bc6f03c90a9d3dcedd Binary files /dev/null and b/tools/beatsyncer/__pycache__/beatsyncer_tool.cpython-311.pyc differ diff --git a/tools/beatsyncer/__pycache__/beatsyncer_tool.cpython-312.pyc b/tools/beatsyncer/__pycache__/beatsyncer_tool.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1d9f10e58769410050683768edf313377bb7671 Binary files /dev/null and b/tools/beatsyncer/__pycache__/beatsyncer_tool.cpython-312.pyc differ diff --git a/tools/beatsyncer/__pycache__/beatsyncer_ui.cpython-311.pyc b/tools/beatsyncer/__pycache__/beatsyncer_ui.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a443f2f731584a0006cd814f38191daf5d44f47 Binary files /dev/null and b/tools/beatsyncer/__pycache__/beatsyncer_ui.cpython-311.pyc differ diff --git a/tools/beatsyncer/__pycache__/beatsyncer_ui.cpython-312.pyc b/tools/beatsyncer/__pycache__/beatsyncer_ui.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a7af734268a487136ea7bb51eaa50882a35d519 Binary files /dev/null and b/tools/beatsyncer/__pycache__/beatsyncer_ui.cpython-312.pyc differ diff --git a/tools/beatsyncer/beatsyncer_tool.py b/tools/beatsyncer/beatsyncer_tool.py new file mode 100644 index 0000000000000000000000000000000000000000..edd6fa2bef518ada4c73df2c6c7627eb6c7b17ab --- /dev/null +++ b/tools/beatsyncer/beatsyncer_tool.py @@ -0,0 +1,470 @@ +import os +import random +import librosa +import numpy as np +from moviepy import * + +class BeatSyncer: + AUDIO_EXTENSIONS = {'.mp3', '.wav', '.m4a', '.aac', '.ogg', '.flac'} + VIDEO_EXTENSIONS = {'.mp4', '.avi', '.mov', '.mkv', '.wmv', '.flv'} + + def __init__(self, audio_filename): + # Construct the full path to the audio file relative to the script's directory + script_dir = os.path.dirname(os.path.abspath(__file__)) + input_path = os.path.join(script_dir, audio_filename) + + # Convert to audio if needed + if self._is_video_file(input_path): + print(f"Converting video file '{input_path}' to audio...") + audio_path = self._convert_to_audio(input_path) + self.audio_path = audio_path + elif self._is_audio_file(input_path): + self.audio_path = input_path + else: + raise ValueError(f"Unsupported file format. File must be one of: {self.AUDIO_EXTENSIONS.union(self.VIDEO_EXTENSIONS)}") + + self.y = None # Audio samples + self.sr = None # Sample rate + self.beat_times = None + self.beat_drop_times = None + + def _is_audio_file(self, filepath): + """Check if the file is an audio file based on its extension.""" + return os.path.splitext(filepath)[1].lower() in self.AUDIO_EXTENSIONS + + def _is_video_file(self, filepath): + """Check if the file is a video file based on its extension.""" + return os.path.splitext(filepath)[1].lower() in self.VIDEO_EXTENSIONS + + def _convert_to_audio(self, video_path): + """Convert a video file to audio and return the path to the audio file.""" + try: + # Create tmp directory in project root if it doesn't exist + script_dir = os.path.dirname(os.path.abspath(__file__)) + tmp_dir = os.path.join(script_dir, 'tmp') + os.makedirs(tmp_dir, exist_ok=True) + + # Create audio filename in tmp directory + base_name = os.path.basename(video_path) + audio_name = os.path.splitext(base_name)[0] + '.mp3' + audio_path = os.path.join(tmp_dir, audio_name) + + # Convert video to audio + video = VideoFileClip(video_path) + video.audio.write_audiofile(audio_path) + video.close() + + return audio_path + except Exception as e: + raise ValueError(f"Error converting video to audio: {str(e)}") + + def load_audio(self, duration_minutes=None): + """ + Load the audio file using librosa, limiting to duration_minutes if provided. + """ + try: + self.sr = 44100 # Set a standard sample rate + if duration_minutes is not None: + duration_seconds = duration_minutes * 60 + else: + duration_seconds = None # Load full audio + self.y, _ = librosa.load(self.audio_path, sr=self.sr, duration=duration_seconds) + if self.y is None or len(self.y) == 0: + raise ValueError("Audio data is empty.") + duration = len(self.y) / self.sr + print(f"Loaded audio file '{self.audio_path}' with duration {duration:.2f} seconds.") + except Exception as e: + print(f"An error occurred while loading the audio file: {e}") + raise + + def detect_beat_drops(self, sensitivity='medium'): + """ + Improved beat detection using onset_strength_multi and adjusted parameters. + + Args: + sensitivity (str): Detection sensitivity level ('low', 'medium', or 'high'). + - low: Less sensitive, detects only major beat drops + - medium: Balanced sensitivity (default) + - high: More sensitive, detects subtle beat changes + """ + print("Detecting beat drops...") + if self.y is None or len(self.y) == 0: + raise ValueError("Audio data is empty. Cannot perform beat detection.") + + # Map sensitivity levels to parameters + sensitivity_params = { + 'low': { + 'hop_length': 512, # Larger hop length for less granular detection + 'std_multiplier': 1.0, # Higher threshold for beat detection + 'min_interval': 0.05 # Longer minimum interval between beats + }, + 'medium': { + 'hop_length': 256, # Default hop length + 'std_multiplier': 0.5, # Default threshold + 'min_interval': 0.02 # Default minimum interval + }, + 'high': { + 'hop_length': 128, # Smaller hop length for more granular detection + 'std_multiplier': 0.25, # Lower threshold for beat detection + 'min_interval': 0.01 # Shorter minimum interval between beats + } + } + + # Validate and get sensitivity parameters + sensitivity = sensitivity.lower() + if sensitivity not in sensitivity_params: + raise ValueError(f"Invalid sensitivity level. Must be one of: {list(sensitivity_params.keys())}") + + params = sensitivity_params[sensitivity] + + # Parameters for onset detection + hop_length = params['hop_length'] + + params.update({ + "detrend": True, + "tightness": 100 + }) + + # Compute the onset envelope using onset_strength_multi with adjusted parameters + onset_env_multi = librosa.onset.onset_strength_multi( + y=self.y, + sr=self.sr, + hop_length=params["hop_length"], + aggregate=np.median, + lag=1, + max_size=1, + detrend=params["detrend"], + center=True, + ) + + # Sum across channels if multi-channel audio + onset_env = onset_env_multi.mean(axis=0) + + # Beat tracking with adjusted parameters + tempo, beats = librosa.beat.beat_track( + onset_envelope=onset_env, + sr=self.sr, + hop_length=params["hop_length"], + tightness=params["tightness"], + units='time' + ) + + # Convert tempo to scalar if it's an array + tempo = float(np.mean(tempo)) if isinstance(tempo, np.ndarray) else float(tempo) + + # Adjust tempo if it's unusually high (e.g., over 180 BPM) + if tempo >= 180: + tempo /= 2 + print(f"Adjusted tempo: {tempo:.2f} BPM") + else: + print(f"Detected tempo: {tempo:.2f} BPM") + + self.beat_times = beats + + if len(self.beat_times) == 0: + raise ValueError("No beats detected in the audio.") + + # Compute the difference in onset envelope with sensitivity-based threshold + onset_diff = np.diff(onset_env) + threshold = np.mean(onset_diff) + params['std_multiplier'] * np.std(onset_diff) + beat_drop_indices = np.where(onset_diff > threshold)[0] + 1 + + # Convert beat frames to times + onset_times = librosa.frames_to_time( + np.arange(len(onset_env)), sr=self.sr, hop_length=hop_length + ) + + # Map beat drop indices to times + beat_drop_times = onset_times[beat_drop_indices] + + # Remove duplicate beat times + self.beat_drop_times = np.unique(beat_drop_times) + + # Remove beat drops that are too close together based on sensitivity + min_interval = params['min_interval'] + filtered_beat_drop_times = [self.beat_drop_times[0]] + for bd_time in self.beat_drop_times[1:]: + if bd_time - filtered_beat_drop_times[-1] >= min_interval: + filtered_beat_drop_times.append(bd_time) + self.beat_drop_times = np.array(filtered_beat_drop_times) + + print(f"Detected {len(self.beat_drop_times)} beat drops with {sensitivity} sensitivity.") + + def _get_media_files(self, media_dir): + """ + Get list of media files from the directory. + Returns a shuffled list of absolute paths to media files. + """ + supported_extensions = ('.mp4', '.avi', '.mov', '.png', '.jpg', '.jpeg', '.gif', '.mkv') + media_files = [] + + # Walk through directory + for root, _, files in os.walk(media_dir): + for file in files: + if file.lower().endswith(supported_extensions): + full_path = os.path.join(root, file) + media_files.append(full_path) + + if not media_files: + raise ValueError(f"No supported media files found in directory: {media_dir}") + + # Shuffle the media files list + random.shuffle(media_files) + print(f"Found and shuffled {len(media_files)} media files") + + return media_files + + def _calculate_text_duration(self, text): + """ + Calculate the duration a text should be displayed based on its word count. + + Args: + text (str): The text to be displayed + + Returns: + float: Duration in seconds + """ + # Count words (split by whitespace) + word_count = len(text.split()) + + # Base duration calculation: + # - Minimum duration: 2 seconds + # - Add 0.5 seconds per word + # - Maximum duration: 8 seconds + duration = min(max(2, word_count * 0.5), 8) + + return duration + + def _wrap_text(self, text, max_chars_per_line=30): + """ + Wrap text to ensure it fits nicely on screen. + + Args: + text (str): Text to wrap + max_chars_per_line (int): Maximum characters per line + + Returns: + str: Wrapped text with newlines and increased line spacing + """ + words = text.split() + lines = [] + current_line = [] + current_length = 0 + + for word in words: + word_length = len(word) + if current_length + word_length + len(current_line) <= max_chars_per_line: + current_line.append(word) + current_length += word_length + else: + lines.append(' '.join(current_line)) + current_line = [word] + current_length = word_length + + if current_line: + lines.append(' '.join(current_line)) + + # Add extra line spacing by using double newlines + return '\n'.join(lines) + + def sync_with_media_directory(self, media_dir, output_path, duration_minutes=None, video_size=None, dark_overlay=0.3, + text_overlays=None): + """ + Create a video by syncing media files with beat drops. + Args: + media_dir: Directory containing images and videos + output_path: Path where the output video will be saved + duration_minutes: Desired total duration in minutes + video_size: Tuple of (width, height) for output video + dark_overlay: Opacity of dark overlay (0.0 to 1.0, where 1.0 is completely black) + text_overlays: List of text strings or tuples (text, duration_seconds). + If only text is provided, duration will be calculated based on word count. + """ + print("Creating video from media files...") + media_files = self._get_media_files(media_dir) + + if not media_files: + raise ValueError(f"No media files found in directory: {media_dir}") + + # Prepare clips with dark theme + clips = [] + + # Set desired_total_duration + if duration_minutes is not None: + desired_total_duration = duration_minutes * 60 # Convert minutes to seconds + else: + desired_total_duration = len(self.y) / self.sr # Use the length of the audio + + # Limit the beat_drop_times to the desired duration + beat_drop_times = self.beat_drop_times[self.beat_drop_times <= desired_total_duration] + + if len(beat_drop_times) == 0: + raise ValueError("No beat drops detected within the desired duration.") + + # Include the start (0) and end times to cover the entire duration + beat_times_full = np.concatenate(([0], beat_drop_times, [desired_total_duration])) + + num_media = len(media_files) + media_index = 0 + + synced_clips = [] + + # Define the desired clip length for initial blank space + initial_clip_duration = 2 # 2 seconds for each clip in the initial blank space + base_scale = 1.2 # resized factor to make clips larger than the video size + zoom_factor = 0.009 # Zoom factor for zoom effects + + # Loop over intervals between beat times to cover entire duration + for i in range(len(beat_times_full) - 1): + start_time = beat_times_full[i] + end_time = beat_times_full[i + 1] + interval_duration = end_time - start_time + + if i == 0 and interval_duration > initial_clip_duration: + # Handle the initial blank space with multiple clips + num_initial_clips = int(np.ceil(interval_duration / initial_clip_duration)) + for j in range(num_initial_clips): + clip_start_time = start_time + j * initial_clip_duration + clip_end_time = min(clip_start_time + initial_clip_duration, end_time) + clip_duration = clip_end_time - clip_start_time + + media_file = media_files[media_index % num_media] + media_index += 1 + + if media_file.lower().endswith(('.mp4', '.avi', '.mov')): + # It's a video file + clip = VideoFileClip(media_file).subclipped(0, clip_duration).resized(base_scale) + + else: + # It's an image file + clip = ImageClip(media_file).with_duration(clip_duration).resized(base_scale) + + + # cropped the clip to the desired video size + clip = clip.cropped(width=video_size[0], height=video_size[1], x_center=clip.w / 2, + y_center=clip.h / 2) + + # Set the start time of the clip + clip = clip.with_start(clip_start_time) + + # Apply random zoom in or zoom out effect with slower zoom + zoom_type = random.choice(['zoom_in', 'zoom_out', None]) + # if zoom_type == 'zoom_in': + # # Slow down zoom in effect + # clip = clip.with_effects([vfx.Resize(lambda t: 1 + zoom_factor * (t / clip.duration))]) + # elif zoom_type == 'zoom_out': + # # Slow down zoom out effect + # clip = clip.with_effects([vfx.Resize(lambda t: 1 + zoom_factor * (1 - t / clip.duration))]) + + # Add random transition to the clip + if synced_clips: + transition_type = random.choice(['crossfadein', 'fadein', None]) + if transition_type == 'crossfadein': + # clip = clip.crossfadein(0.1) + print() + elif transition_type == 'fadein': + # clip = clip.fadein(0.1) + print() + + synced_clips.append(clip) + else: + # Handle regular intervals between beat drops + media_file = media_files[media_index % num_media] + media_index += 1 + + if media_file.lower().endswith(('.mp4', '.avi', '.mov')): + # It's a video file + clip = VideoFileClip(media_file).subclipped(0, interval_duration).resized(base_scale) + + + else: + # It's an image file + clip = ImageClip(media_file).with_duration(interval_duration).resized(base_scale) + + + # cropped the clip to the desired video size + clip = clip.cropped(width=video_size[0], height=video_size[1], x_center=clip.w / 2, y_center=clip.h / 2) + + # Set the start time of the clip + clip = clip.with_start(start_time) + + # Apply random zoom in or zoom out effect with slower zoom + zoom_type = random.choice(['zoom_in', 'zoom_out', None]) + # if zoom_type == 'zoom_in': + # # Slow down zoom in effect + # clip = clip.with_effects([vfx.Resize(lambda t: 1 + zoom_factor * (t / clip.duration))]) + # elif zoom_type == 'zoom_out': + # # Slow down zoom out effect + # clip = clip.with_effects([vfx.Resize(lambda t: 1 + zoom_factor * (1 - t / clip.duration))]) + + + # If this is the last clip, apply fadeout + if i == len(beat_times_full) - 2: + fade_duration = min(2, clip.duration / 2) + + clip = clip.with_effects([vfx.FadeOut(fade_duration)]) + + synced_clips.append(clip) + + # Create the final video + final_clip = CompositeVideoClip(synced_clips, size=video_size) + + # Create a list of all clips for final composition + all_clips = [final_clip] + + # Add dark overlay if enabled + if dark_overlay > 0: + # Create a black ColorClip with the same size as the video + black_clip = ColorClip(size=video_size, color=(0, 0, 0)) + black_clip = black_clip.with_duration(final_clip.duration) + black_clip = black_clip.with_opacity(dark_overlay) + all_clips.append(black_clip) + + # Add text overlays if provided + if text_overlays: + current_time = 0 + for text_item in text_overlays: + # Handle both string and tuple inputs + if isinstance(text_item, tuple): + text, duration = text_item + else: + text = text_item + duration = self._calculate_text_duration(text) + + # Wrap text if needed + wrapped_text = self._wrap_text(text) + print(f"Adding text: '{wrapped_text}' with duration: {duration:.1f}s") + + # Create text clip with white color and nice font + txt_clip = TextClip(text=wrapped_text, font_size=40, color='white', + font='Arial Black',method='label', interline=-1) # 'label' method handles multiline text better + + # Center the text + txt_clip = txt_clip.with_position(('center', 'center')) + + # Set the duration and start time + txt_clip = txt_clip.with_duration(duration) + txt_clip = txt_clip.with_start(current_time) + + # Add fade in/out effects + txt_clip = txt_clip.with_effects([vfx.FadeIn(0.5), vfx.FadeOut(0.5)]) + + all_clips.append(txt_clip) + current_time += duration + + # Create final composition with all layers + final_clip = CompositeVideoClip(all_clips, size=video_size) + + # Set the audio of the final clip + audio_clip = AudioFileClip(self.audio_path).subclipped(0, desired_total_duration) + # Apply audio fadeout at the end + fade_duration = min(2, audio_clip.duration / 2) + audio_clip = audio_clip.with_effects([afx.AudioFadeOut(fade_duration)]) + final_clip = final_clip.with_audio(audio_clip) + final_clip = final_clip.with_duration(desired_total_duration) + + # Write the output video file + final_clip.write_videofile( + output_path, fps=30, threads=32, audio_codec="aac", preset='ultrafast' + ) + print(f"Output video saved to '{output_path}'") diff --git a/tools/beatsyncer/beatsyncer_ui.py b/tools/beatsyncer/beatsyncer_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..ccd24962e7e3fe0669e3446835dd3a6fbaf23794 --- /dev/null +++ b/tools/beatsyncer/beatsyncer_ui.py @@ -0,0 +1,151 @@ +import os +import gradio as gr + +import tempfile +import shutil + +from tools.beatsyncer.beatsyncer_tool import BeatSyncer + +class BeatsyncerUI: + def __init__(self): + self.temp_dir = tempfile.mkdtemp() + # Use the existing clips directory from the project + script_dir = os.path.dirname(os.path.abspath(__file__)) + self.clips_dir = os.path.join(script_dir, 'clips') + + def process_video( + self, + audio_file, + duration_minutes, + sensitivity, + video_height, + dark_overlay, + text_input, + progress=gr.Progress() + ): + try: + # Save audio file + audio_path = os.path.join(self.temp_dir, "input_audio" + os.path.splitext(audio_file.name)[1]) + shutil.copy(audio_file.name, audio_path) + + # Process text overlays + text_overlays = [text.strip() for text in text_input.split('\n') if text.strip()] + + # Calculate video dimensions + video_width = int((video_height * 9) / 16) # 16:9 aspect ratio + + # Set output path + output_path = os.path.join(self.temp_dir, "output_video.mp4") + + # Convert duration to float if provided + if duration_minutes and duration_minutes.strip(): + duration_minutes = float(duration_minutes) + else: + duration_minutes = None + + progress(0.1, desc="Initializing BeatSyncer...") + + # Initialize BeatSyncer + beat_syncer = BeatSyncer(audio_path) + + progress(0.2, desc="Loading audio...") + beat_syncer.load_audio(duration_minutes=duration_minutes) + + progress(0.3, desc="Detecting beat drops...") + beat_syncer.detect_beat_drops(sensitivity=sensitivity) + + progress(0.4, desc="Creating video...") + beat_syncer.sync_with_media_directory( + self.clips_dir, # Using the existing clips directory + output_path, + duration_minutes=duration_minutes, + video_size=(video_width, video_height), + dark_overlay=float(dark_overlay), + text_overlays=text_overlays + ) + + progress(1.0, desc="Done!") + return output_path + + except Exception as e: + raise gr.Error(f"Error: {str(e)}") + + def create_ui(self): + with gr.Blocks(theme=gr.themes.Soft()) as app: + gr.Markdown(""" + # 🎵 BeatSyncer - Video Creation Tool + Create amazing videos synchronized with music beats! + + Using media files from: {} + """.format(self.clips_dir)) + + with gr.Row(): + with gr.Column(scale=2): + audio_input = gr.File( + label="Upload Audio/Video File", + file_types=[".mp3", ".wav", ".m4a", ".aac", ".ogg", ".flac", ".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv"] + ) + + + duration = gr.Textbox( + label="Duration (minutes, optional)", + placeholder="Leave empty for full audio duration" + ) + + sensitivity = gr.Radio( + choices=["low", "medium", "high"], + value="medium", + label="Beat Detection Sensitivity" + ) + + video_height = gr.Slider( + minimum=480, + maximum=1920, + value=720, + step=120, + label="Video Height (16:9 ratio)" + ) + + dark_overlay = gr.Slider( + minimum=0.0, + maximum=1.0, + value=0.3, + step=0.1, + label="Dark Overlay Intensity" + ) + + text_input = gr.Textbox( + label="Text Overlays (one per line)", + placeholder="Enter text overlays...", + lines=5 + ) + + create_btn = gr.Button("Create Video", variant="primary") + + with gr.Column(scale=1): + output_video = gr.Video( + label="Output Video", + height=640, # Fixed height for preview + width=360 # Fixed width for preview + ) + + create_btn.click( + fn=self.process_video, + inputs=[ + audio_input, + duration, + sensitivity, + video_height, + dark_overlay, + text_input + ], + outputs=output_video + ) + + return app + +# Create and launch the UI +if __name__ == "__main__": + ui = BeatSyncerUI() + demo = ui.create_ui() + demo.launch(debug=True, share=False, inbrowser=True) \ No newline at end of file diff --git a/tools/beatsyncer/clips/.DS_Store b/tools/beatsyncer/clips/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0933e2c2a5349beb17e6a16b405e85127aa44dde Binary files /dev/null and b/tools/beatsyncer/clips/.DS_Store differ diff --git a/tools/beatsyncer/clips/1657591309536.mp4 b/tools/beatsyncer/clips/1657591309536.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..c45cfaa6d1c6954873fc06c00de947a4075637c8 --- /dev/null +++ b/tools/beatsyncer/clips/1657591309536.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69aa9858fca54bda6ac78abe43cf4bd3081ef0d6a69e433603cfe3995219c97c +size 7222278 diff --git a/tools/beatsyncer/clips/1657591680538.mp4 b/tools/beatsyncer/clips/1657591680538.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..193b20b825fdb58a7a7436a25ae5245d0c7ebf7b --- /dev/null +++ b/tools/beatsyncer/clips/1657591680538.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c531723d9ba697e7453cbc61d772e76957fca7ae83f2ecacaae4b18dd770d7d4 +size 40783923 diff --git a/tools/beatsyncer/clips/1657591832727.mp4 b/tools/beatsyncer/clips/1657591832727.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..45634ee79eb4723124842f4c3b262b3bc665ebb3 --- /dev/null +++ b/tools/beatsyncer/clips/1657591832727.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37be14aab321518e220315f3673dc63113b9a601ae96d246d614b36ea9993a63 +size 19891527 diff --git a/tools/beatsyncer/clips/1657616409629.mp4 b/tools/beatsyncer/clips/1657616409629.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..23e2847759c9ccd891e6d7407d0d19e97dc96822 --- /dev/null +++ b/tools/beatsyncer/clips/1657616409629.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30401ebf2010942c79f16f39dab912ba195c606959bab9500c52dcfa60039aca +size 10948753 diff --git a/tools/beatsyncer/clips/1657616591950.mp4 b/tools/beatsyncer/clips/1657616591950.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..c1458783f1ecf09c4a95a7d9717272d4fb107ad6 --- /dev/null +++ b/tools/beatsyncer/clips/1657616591950.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:863652f4a1abe5d0d6ee5d0cca92cac3aab6deb7b7d900fa1dfb06289698a274 +size 4494706 diff --git a/tools/beatsyncer/clips/1657616660848.mp4 b/tools/beatsyncer/clips/1657616660848.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..b16ebd918bf58b01a3953d87552f2bfd16165a37 --- /dev/null +++ b/tools/beatsyncer/clips/1657616660848.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09002494bd0c33f02fffecc9ec484da2630dac91399b2ee1adfa22433757b05d +size 8450597 diff --git a/tools/beatsyncer/clips/1657616730783.mp4 b/tools/beatsyncer/clips/1657616730783.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..7968b8b208853b2939bf7602029eb91088c42447 --- /dev/null +++ b/tools/beatsyncer/clips/1657616730783.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1aad238b7f6623ad10af2413ece4b65113ebae42bb87e41cc39d8276864a7c67 +size 6972420 diff --git a/tools/beatsyncer/clips/1657616788731.mp4 b/tools/beatsyncer/clips/1657616788731.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..1d7b8155e88f296ffa104f016f58b987bf837bcc --- /dev/null +++ b/tools/beatsyncer/clips/1657616788731.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca25db432389307b40cd11a945d0b19ad0f72e9e7b9d34b2d8e37565e39beb3d +size 7192248 diff --git a/tools/beatsyncer/clips/1657616954951.mp4 b/tools/beatsyncer/clips/1657616954951.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..08b2065e67638082a7b4a2a2bf2cd8af18b98133 --- /dev/null +++ b/tools/beatsyncer/clips/1657616954951.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5021eb2d204cdc23fb8e938d0de0d6baa99ac1639726e2b0e3fe2e1e6566a7c3 +size 9827398 diff --git a/tools/beatsyncer/clips/1657923632784.mp4 b/tools/beatsyncer/clips/1657923632784.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..48935b359fe295cf62d62cbb535bc2fe9dec0472 --- /dev/null +++ b/tools/beatsyncer/clips/1657923632784.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5837c302c5fe15878b068e22a0ebb1a2fedb747d226ea81a9ba0631fca898540 +size 8853773 diff --git a/tools/beatsyncer/clips/288298605_1687187951639494_579221224540621563_n.mp4 b/tools/beatsyncer/clips/288298605_1687187951639494_579221224540621563_n.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..7727d24bc822bef81ceac6371b39dddc3d9f742f --- /dev/null +++ b/tools/beatsyncer/clips/288298605_1687187951639494_579221224540621563_n.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5277a9bc08a20012285e5c438d043047802f525f82583e5c2d4106ec30da8cc3 +size 2767934 diff --git a/tools/beatsyncer/clips/289438954_5129286887125053_220682033208031067_n.mp4 b/tools/beatsyncer/clips/289438954_5129286887125053_220682033208031067_n.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..df3d00aeb0b7cfbaddf6258c98e9ad96b475f490 --- /dev/null +++ b/tools/beatsyncer/clips/289438954_5129286887125053_220682033208031067_n.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba5f5469980c5df7e4910122e0cf818bd90fe98e3743650ecf189c706230009c +size 6625183 diff --git a/tools/beatsyncer/clips/289568281_1462345327513048_3022742540007505592_n.mp4 b/tools/beatsyncer/clips/289568281_1462345327513048_3022742540007505592_n.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..dc9d994daf7961784f7c1fc67022b1dd3900bb8e --- /dev/null +++ b/tools/beatsyncer/clips/289568281_1462345327513048_3022742540007505592_n.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4801482e63465e85a02fd9a7eacbbe0268954bf9674f55a10aa8c80c9e2753f0 +size 1618834 diff --git a/tools/beatsyncer/clips/290154393_565236208447925_526246679178540543_n.mp4 b/tools/beatsyncer/clips/290154393_565236208447925_526246679178540543_n.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..4a135e2488cfab2332a86fb219ea33edf29987e2 --- /dev/null +++ b/tools/beatsyncer/clips/290154393_565236208447925_526246679178540543_n.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:671438acc46a10e657388d4c13d34dbecf5af5c6f9ae915b24caec154bf3390a +size 3465572 diff --git a/tools/beatsyncer/clips/290702607_425330016154178_254797435320646581_n.mp4 b/tools/beatsyncer/clips/290702607_425330016154178_254797435320646581_n.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..f770383e335645faea19decf0d04d656828e0b7d --- /dev/null +++ b/tools/beatsyncer/clips/290702607_425330016154178_254797435320646581_n.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99d8e8e4ee7391d0dc4d3223e5d5d203cea89089fc507862718fdd203c5234f8 +size 3268380 diff --git a/tools/beatsyncer/clips/290990526_409256130973654_5788653198617875871_n.mp4 b/tools/beatsyncer/clips/290990526_409256130973654_5788653198617875871_n.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..eee41e5dd1fb123c60602b95289e514a0e9d07d6 --- /dev/null +++ b/tools/beatsyncer/clips/290990526_409256130973654_5788653198617875871_n.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:191b27a28f64889c201c22daf0bc48ade99d9902b442c3a2d4e8e1ccaebb5060 +size 2297732 diff --git a/tools/beatsyncer/clips/291032176_120067890734229_1814549935092808193_n.mp4 b/tools/beatsyncer/clips/291032176_120067890734229_1814549935092808193_n.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..3288a956fe877523d833754cbc5584629fe4c9e5 --- /dev/null +++ b/tools/beatsyncer/clips/291032176_120067890734229_1814549935092808193_n.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c69c8c738b98305e0b40fbd63ddb6133b886eaf279e8894e232252dad075fc43 +size 3250400 diff --git a/tools/beatsyncer/clips/657591201007.mp4 b/tools/beatsyncer/clips/657591201007.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..b09e942a6aca19e79c184e49186dc8f9f051f491 --- /dev/null +++ b/tools/beatsyncer/clips/657591201007.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef0f3447388144458427572b64c768ec09cc88b87635fbece3192cc2eb973975 +size 11149649 diff --git a/tools/beatsyncer/clips/657591253340.mp4 b/tools/beatsyncer/clips/657591253340.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..fcdef13fcca2ba53a4b91bfa4c4bdee76930c286 --- /dev/null +++ b/tools/beatsyncer/clips/657591253340.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1d3c5bd6614ea8e72703a4f94e7590c29a792b9eb0f22504b0f5bb663efbdbb +size 6907398 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_001.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_001.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..6f678c6be9e80825fca325a7f2e470131f9ca0b0 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_001.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9340f70c21ebebfa4230c3891198b278b849a8a3f788bf7f7edd36285f89e830 +size 2103745 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_002.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_002.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..0cde633fe1ecf575b564a47238d99b429047860e --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_002.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2fff2e4e65479a29a450cdcb340c78564f5794167ee03352dcdc2d8fe856930 +size 155997 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_003.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_003.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..9cd4d7a08cc839a2f90677dc6684095922da244b --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_003.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:762d0ffc6a657e3c5b1c190190a9b6a3d3ea7a9979239db6de2532658a1dd0b9 +size 350743 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_004.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_004.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..59859b022c4d0efdc4aa585a886461aca970c4f7 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_004.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0365717355f8e0c8cb39dedbbba0553c1e6132fd2e793972336c2581933eb4d0 +size 563440 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_005.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_005.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..e6f838608c0da3c6e0f353a6c9faeb73885431db --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_005.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf5ba4edc9c23e961ef5d3379790b0e32b573b314fee099de74b3b14b841a3d1 +size 145942 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_006.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_006.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..f9ccd1280eec4a3b64934a500a5c9528cfa86363 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_006.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:174b2c1e98000a026427acde3bdde0914f60ecabfbab8ac93be5dd81a4cf3bc8 +size 282620 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_007.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_007.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..a951bf465d45d507db55d7e9615f6f3b9a8fe91b --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_007.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f56a247020f35d3ce480f106605fdd704cfddb7513f0b52d7f63646c6e5be4d9 +size 353519 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_008.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_008.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..1283974fe3e90e53e8e459b54b94852a9420a678 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_008.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:711df5b60e0ced8b818a49f32e6ca516193346a3b4ce63bb2188d3802f62aace +size 343334 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_009.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_009.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..4b302b8307affc7d776b3dc8f042791c3b7c5c53 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_009.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab88f7973f2fee383ba852f55d46912aaee7ad849a21a46cdaedb8e957046c6f +size 204924 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_010.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_010.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..e8f7cdbf945bf4ff508c0f2582244a5dc0f0dead --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4333A1E1AE5B90482FB1125369C59B_video_dashinit_scene_010.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ed2910d6e2e6b4159ba3290053526fa12573082a18dddd38f3d2eebc50ce15f +size 144843 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_001.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_001.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..4ead1ba0979abddb704ca6ffce2db07f62304936 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_001.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b73792168a06223614b33179844a458bd8389f426980414f47ea0389f57cba2 +size 1138637 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_002.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_002.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..4e74dd50e8ae6229f617ef640fe247c659d9d739 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_002.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85bcdde3e764e65ca3260b20c17918581dfce3acec4eb04e4813a2ac53e16587 +size 566912 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_003.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_003.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..79b5800d124d5aa74d18756c0c21e082a252fd88 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_003.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:baa62fca4618f79cb7be3b2158509fc97f2f9e7f82991300b6e19b42df217a4d +size 544288 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_004.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_004.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..406a73841fd5b1f9fe8c660743b50405b5225681 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_004.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:102282c000ac3021d2c6749d0164663e936a6e07655d56c1850015fdb148a292 +size 915749 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_005.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_005.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..d5e2e4c8dad5b38730a8a330dc0ba02dc7b0218d --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_005.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:677c65cb2eb4dc4bcbab485c0d9c21eec605fcb81ece23fc93545ed495180c79 +size 1653275 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_006.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_006.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..ef2a4d34037723849bab9809fb0a1a8f551b4719 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_006.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8297e4e79175c5029cb0021039466d6368f30d1d4409106e3acfd8a953e5c58 +size 820349 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_007.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_007.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..6f24bf80817a72aba8261d68887a789cccd5c4f1 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_2B4E29561A51827AF04D3C92BDED06B5_video_dashinit_scene_007.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc97a361411f3180cb4589903566191e0ac3140856101b51beef3f71b1fec858 +size 813675 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_001.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_001.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..7c4d93a169291ecbf17dab826ad4e5821abc01a6 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_001.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f4f03c0afab306a75821fe4813057e29096f7675ec996cf4183331b1349e746 +size 324818 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_002.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_002.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..dcbf346ac8bdea03942797459ca30725ddaf7659 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_002.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1a7875fc230877dc96276d2d7657133ab425b9528f364bbe146922301e8191d +size 730156 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_003.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_003.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..8b4a5b2c4a3e84562babf86c4b34f58a7816973b --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_003.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f1dc7bb83d826085cdd4d2ab288f05d4f0e1bedb198b1a1a822dd77563180d8 +size 439700 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_004.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_004.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..7e814745dc727cd9c420c41c0ff29bb4ef350af1 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_004.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6f4281fed8eb79ec922038da4544ce46be6cc360703b2383f2cbfc88345a3f3 +size 302739 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_005.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_005.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..800d7a56b24efdadc6406740463f1f960b4794fc --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_005.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c106b4019465de406077d42afeb010aa1de43fcb3603707b66cd59b9f8945f6b +size 561379 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_006.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_006.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..8608f5430aa5ac1b08b140220c73f696c8f31174 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_3547B2AC7669AF7E434D64AFDC9A6C9F_video_dashinit_scene_006.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d3c5ace6a08ea56c81716b6f30286124d683eb6c3b39d0d6398c3d8d094d991 +size 382204 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_6F4E14515715C2DB44E4959237066298_video_dashinit_scene_001.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_6F4E14515715C2DB44E4959237066298_video_dashinit_scene_001.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..6d096e441eaacb0e454968902113c098470db7d1 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_6F4E14515715C2DB44E4959237066298_video_dashinit_scene_001.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:744efec1722396dc5143dbf7c0bcf82c6a0ba9e84856a23f805e3714435bc92a +size 3038574 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_6F4E14515715C2DB44E4959237066298_video_dashinit_scene_002.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_6F4E14515715C2DB44E4959237066298_video_dashinit_scene_002.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..fcd6142c3ef88e37083cfc4016ac033bda1bc8f4 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_6F4E14515715C2DB44E4959237066298_video_dashinit_scene_002.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28aa5290f27ed1932b6a38b6d724c2ba4f026c3ac65d05dd012e5f2b52d66809 +size 2173169 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_7944DC0BB36028CA2D36023098F3029C_video_dashinit.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_7944DC0BB36028CA2D36023098F3029C_video_dashinit.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..97be4c6e0f5e8c45b973c73b9fa063bfecb37b50 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_7944DC0BB36028CA2D36023098F3029C_video_dashinit.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:746b1ad01affae459d6aac7fefa1088fb7d7a21d5d9e9b4a04c7731b781e9bfc +size 4166090 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_001.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_001.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..83af318ce009ec8673dba5c6b05671c5d57030ad --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_001.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a2b8e7985767b31c524c994fd9c9fd33051157b00c9aab3cb2a397592bd61f9 +size 485941 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_002.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_002.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..718be4014f5093f68d667416d1328cb5afde54d4 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_002.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a746a0cd1404a122c5bf3ddddff42d4eb53fec8ec4de924ec9a01338a34e8e6e +size 489915 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_003.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_003.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..94df5adca967b27f57756a78d8fb60cef1feed19 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_003.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db938b641d85013311027904c60c677304c97d8068e57ccd7dc4c16d1a835c5b +size 418039 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_004.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_004.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..64cd1c9d5511ee5b4e160aad5f225a0837a1636e --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_004.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98ef580dbb51cc18696454c3085a23e92c0d9d81bd952bf9c8fa702fcae0e9a4 +size 178086 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_005.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_005.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..ab1dd724e329879ce074d5deb23f240f1bfd85b4 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_005.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5280d3fabab8f340dfbec7a6a38f1ef935729f4bcf487a9d1c9962bd59da0e8b +size 456687 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_006.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_006.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..88601696d7d6117509b86f1996117f59e1d39448 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_006.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:938cce3a14599b31f019392b60cef471a0772976ac9ad0e8f1ba99e170b5b79a +size 272429 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_007.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_007.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..9b4d95b523d5d07c974029cbaa252b0d096a3aca --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_007.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2de0949df664c4c620df3b104303683e36ddd020ba5dde44c4a137021ae18345 +size 395515 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_008.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_008.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..4eee0a00d43b2957ecfa6689f9e57021ba02c9c2 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_008.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:571da24fa531e235fb6a5165db7c62f88c326cc2c2e74e1b6a84d989aa95791d +size 190197 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_009.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_009.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..bc12c0e4640edd52ff1c02032d6ae8584db4e9ba --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_009.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20b73a994dd292b3ca6b3ac3e9a6b33d749ae707a36c21ef0b0b4041545eb076 +size 298354 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_010.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_010.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..887811d582e81468591ff6f6e9e9f8d68cd98f93 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_010.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae135ac6c0e734f8ea3d3164ba5ef89f8c282ff1e9661b8f376a9b9eb52925b9 +size 616945 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_011.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_011.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..c5457e78ecbd772892b608dc9046be1f65f13da4 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_011.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84926ba00331659433a0d58d7f5e845111767d646e1ae3f092769d55018202cf +size 329815 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_012.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_012.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..45999d00a21191ef025d14b28c69658036f7a154 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_012.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cda09142f9fdc01c204068726f46b42e6e3c470e798bc031af412a78a484fd8f +size 316734 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_013.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_013.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..d5ee91ef606edc7aeeee61c9f802d2c4b966ffa4 --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_013.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37952ba40f722803f050c5f1439fc726c758fbc17cd517e635d3edc76460e915 +size 414335 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_014.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_014.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..a318303a2b6374f0769b37a86f6294711c0df3fe --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_014.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a54dbb901ce3fa5a2a185c78bcf1e50dcd99d12a318931464394909dea4c17c1 +size 399673 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_015.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_015.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..e5ae76c278703cea5f160dc13c719ee57d48894c --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_015.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:283836d35460ca1fd3df50f11accbeb6a704c1903e58bbd3ce3ac78a7eaf2d58 +size 250265 diff --git a/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_016.mp4 b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_016.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..92572ab0ebd95c3c50ab189f8b2abb58dc82193d --- /dev/null +++ b/tools/beatsyncer/clips/Snapinsta.app_video_E544C6323B8E4CAB778F010CB59E0D88_video_dashinit_scene_016.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3aa7163a6fc8bef96ad94d92ff68d40a94ae3314d1614517a2193408b6184bfc +size 512814 diff --git a/tools/beatsyncer/clips/ai-generated-8079926_1280.png b/tools/beatsyncer/clips/ai-generated-8079926_1280.png new file mode 100644 index 0000000000000000000000000000000000000000..0b8b78978ee4d1bc939c68ee80c9d5db3f21b198 --- /dev/null +++ b/tools/beatsyncer/clips/ai-generated-8079926_1280.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17833b1d83e2e696579216bc9f8b9c2fac95faf0575b340416ead9e266dcfa91 +size 1961454 diff --git a/tools/beatsyncer/clips/ai-generated-8601539_1280.jpg b/tools/beatsyncer/clips/ai-generated-8601539_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..50ecec428cfd4b631466171c563a50f6291dfebe --- /dev/null +++ b/tools/beatsyncer/clips/ai-generated-8601539_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3906d8cf7e9550fbd3b05c68fe765f27a8bfcceabad9331be437c4fdedbe1797 +size 216003 diff --git a/tools/beatsyncer/clips/ai-generated-9151678_1280.jpg b/tools/beatsyncer/clips/ai-generated-9151678_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4fd05ffa33baa906a078510b0bc421dda7582ac3 --- /dev/null +++ b/tools/beatsyncer/clips/ai-generated-9151678_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:603dd2be0b27599561ed4800cfd847b27d0a66b25bbc4b0a5c9016dacdcf65a1 +size 137560 diff --git a/tools/beatsyncer/clips/antique-car-7577214_1280.jpg b/tools/beatsyncer/clips/antique-car-7577214_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cba4a4ee1491d4b7b24dfc3f0cf78507ddac98b3 --- /dev/null +++ b/tools/beatsyncer/clips/antique-car-7577214_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:090d0179cbb679234bce2e1bd521fc8c7466a991a794a3cec200b51041e777af +size 167880 diff --git a/tools/beatsyncer/clips/automobile-1850862_1280.jpg b/tools/beatsyncer/clips/automobile-1850862_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6bf84daadfa52d7ed96cc77b59bfbdd2c76175cd --- /dev/null +++ b/tools/beatsyncer/clips/automobile-1850862_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7de07654b576da6f108b950af43322bd47588f507d5186b3f49efbe572ba7296 +size 333222 diff --git a/tools/beatsyncer/clips/bmw-6236208_1280.jpg b/tools/beatsyncer/clips/bmw-6236208_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..38034616d1012662626e6a5170b58f58d1427228 --- /dev/null +++ b/tools/beatsyncer/clips/bmw-6236208_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bac47669253ed1ad483da83dd8af68626351aab886f0f7e5a089be11c011111b +size 159486 diff --git a/tools/beatsyncer/clips/car-171415_1280.jpg b/tools/beatsyncer/clips/car-171415_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..64a218bb0a5680285b61cc2c06006aa53d26c8ff --- /dev/null +++ b/tools/beatsyncer/clips/car-171415_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:393d00a235e920e0438e4c898f95b4fcdcf7b2fee91eeb79e97958c4b1a368b6 +size 251656 diff --git a/tools/beatsyncer/clips/car-219813_1280.jpg b/tools/beatsyncer/clips/car-219813_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..35a6b9873e63fd879e9febebaef75585e2be47a9 --- /dev/null +++ b/tools/beatsyncer/clips/car-219813_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df1590bd4f0be0b115dd8ac7fab20c5e419dbe54488bc6a043e1996a513e6d17 +size 296910 diff --git a/tools/beatsyncer/clips/car-5548243_1280.jpg b/tools/beatsyncer/clips/car-5548243_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d344dc9c67201d0fd35847c0ebf1ceefa9f8ac76 --- /dev/null +++ b/tools/beatsyncer/clips/car-5548243_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e235172f175245abb0c1230635f6087d89c8ef3b7a6a864cc8f7a66d4897b4e +size 184266 diff --git a/tools/beatsyncer/clips/car-5548244_1280.jpg b/tools/beatsyncer/clips/car-5548244_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dc1d55bb327564488890bfd0e903444bc14da257 --- /dev/null +++ b/tools/beatsyncer/clips/car-5548244_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82ab09060e1f89241514e65fab9ce7fcc4e04896b4dc3189cec0e912d4f3aa5f +size 174652 diff --git a/tools/beatsyncer/clips/car-6306695_1280.jpg b/tools/beatsyncer/clips/car-6306695_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..07fac9c2aa50635f6311464b6ce08567d623ae42 --- /dev/null +++ b/tools/beatsyncer/clips/car-6306695_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7189b1652e1ae61c159980e0afdaf5bc4081e52ee1c69a28f6324ef6dc5dc79 +size 350032 diff --git a/tools/beatsyncer/clips/car-6483720_1280.jpg b/tools/beatsyncer/clips/car-6483720_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6da63490218334e5f14d88ee1bf01ffac79487ea --- /dev/null +++ b/tools/beatsyncer/clips/car-6483720_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4362ec001e7980d4bda7648187337321803667fb58f22bd7e0f55f8b546bc948 +size 378868 diff --git a/tools/beatsyncer/clips/car-6589240_1280.jpg b/tools/beatsyncer/clips/car-6589240_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9480b7333edc811d6f2b6c64f88f4861d88572bf --- /dev/null +++ b/tools/beatsyncer/clips/car-6589240_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a0a61b9220b07d35ba65322a7e198c7d939bfdfaf67ccf3ac5a4c7da1c17367 +size 303340 diff --git a/tools/beatsyncer/clips/car-6658749_1280.jpg b/tools/beatsyncer/clips/car-6658749_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2a49a2e350f3f15bcf2bd266d8d70bc972bc79b0 --- /dev/null +++ b/tools/beatsyncer/clips/car-6658749_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5271536a3da5173f6110b0af11250bfc6ae827d4cfe6397d96b7002198c179d0 +size 311605 diff --git a/tools/beatsyncer/clips/car-7139944_1280.jpg b/tools/beatsyncer/clips/car-7139944_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9a0e0bbfc3b86687fc0ea1e0c9f393fd6f5f03ff --- /dev/null +++ b/tools/beatsyncer/clips/car-7139944_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3602f55c14ff323ecd8b53d903cbfb9900932e70cd5f7511506405be32ec98dc +size 243122 diff --git a/tools/beatsyncer/clips/car-7203265_1280(1).jpg b/tools/beatsyncer/clips/car-7203265_1280(1).jpg new file mode 100644 index 0000000000000000000000000000000000000000..1e45abb631974c67bf21cdac042b1588abf2317a --- /dev/null +++ b/tools/beatsyncer/clips/car-7203265_1280(1).jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b90ff21aac0e87ee4e68fdce47d3e383105128bbfa134fe60a11cb4a75b0d9c +size 136040 diff --git a/tools/beatsyncer/clips/car-7203265_1280.jpg b/tools/beatsyncer/clips/car-7203265_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1e45abb631974c67bf21cdac042b1588abf2317a --- /dev/null +++ b/tools/beatsyncer/clips/car-7203265_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b90ff21aac0e87ee4e68fdce47d3e383105128bbfa134fe60a11cb4a75b0d9c +size 136040 diff --git a/tools/beatsyncer/clips/car-7635511_1280.jpg b/tools/beatsyncer/clips/car-7635511_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..88f71dae0d5134e5d7e33807502d6ca61e5e902d Binary files /dev/null and b/tools/beatsyncer/clips/car-7635511_1280.jpg differ diff --git a/tools/beatsyncer/clips/car-7725834_1280.jpg b/tools/beatsyncer/clips/car-7725834_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c732701b0bc8df21ee706d3c7f1d88a81d99fd5e --- /dev/null +++ b/tools/beatsyncer/clips/car-7725834_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d362cf273be65e838ecc50eafe7653628f2508179afe3bc96f5b95c5305776cb +size 226572 diff --git a/tools/beatsyncer/clips/car-7830737_1280.jpg b/tools/beatsyncer/clips/car-7830737_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4780022581f7ced16c75af319fbab1f6fdd3665d --- /dev/null +++ b/tools/beatsyncer/clips/car-7830737_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:071455b19109f32af2f6f942ead5bd3949664c6d918d4d2821f2de40c1044347 +size 160709 diff --git a/tools/beatsyncer/clips/car-8141820_1280.jpg b/tools/beatsyncer/clips/car-8141820_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..47ccd2fbe6734a178d5c198074ece2aa49a483ca --- /dev/null +++ b/tools/beatsyncer/clips/car-8141820_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e186cab540d27d66a31017dd7c9390e4145d74cc178610c318a475e956c3d61 +size 251941 diff --git a/tools/beatsyncer/clips/fashion-1399346_1280.jpg b/tools/beatsyncer/clips/fashion-1399346_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..42e01cbf458799aaa448ba783f6d401b8087e8f7 --- /dev/null +++ b/tools/beatsyncer/clips/fashion-1399346_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4b0963f51cd7760cdbca11ac6013b626c077fdc0fbaa8fa0a3f0d0c155e5d04 +size 294769 diff --git a/tools/beatsyncer/clips/mercedes-benz-7383931_1280.jpg b/tools/beatsyncer/clips/mercedes-benz-7383931_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..05ba148e7b782ba90ac0e8e3070da181984fb274 --- /dev/null +++ b/tools/beatsyncer/clips/mercedes-benz-7383931_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:861b90101d7287e06fbcf241d2da21081df57bfbda76257a39fda2414bf30968 +size 255212 diff --git a/tools/beatsyncer/clips/speedy-car-8391554_1280.jpg b/tools/beatsyncer/clips/speedy-car-8391554_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ae4bfc026629374bb912ecf9cb1f7f093fdf5f50 --- /dev/null +++ b/tools/beatsyncer/clips/speedy-car-8391554_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e39c754e0e77354d74d935f50247f082b901d6b14b1a74634ccc9a7d7af94a3f +size 185268 diff --git a/tools/beatsyncer/clips/sport-4155825_1280.jpg b/tools/beatsyncer/clips/sport-4155825_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9e84558e3772b4d47bc7f041396e49e8ccb3638a --- /dev/null +++ b/tools/beatsyncer/clips/sport-4155825_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7203fbc33cd3a72c032380d1780bdef5379c47738097a4ce767c8a887fd9fd7a +size 271345 diff --git a/tools/beatsyncer/clips/vintage-car-7300881_1280.jpg b/tools/beatsyncer/clips/vintage-car-7300881_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1cea38a7af6b29b83742049e2a6492cc6955f7e1 --- /dev/null +++ b/tools/beatsyncer/clips/vintage-car-7300881_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3557b663e9c98f3241f9ae52afdd5baa93bfdc49714dd8391a1045a78129633f +size 325573 diff --git a/tools/beatsyncer/clips/vintage-rolls-royce-7414656_1280.jpg b/tools/beatsyncer/clips/vintage-rolls-royce-7414656_1280.jpg new file mode 100644 index 0000000000000000000000000000000000000000..508d6d6a02239b1f14ac282119a978ecb3f22f94 --- /dev/null +++ b/tools/beatsyncer/clips/vintage-rolls-royce-7414656_1280.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:454595c576969698435dbdb0daafde92ec7c28339d5c124fedd18dafd2805bf8 +size 170516 diff --git a/tools/beatsyncer/tmp/input_audio.mp3 b/tools/beatsyncer/tmp/input_audio.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0de13507dc5ffc3a53a4484faf0733964d8a505e --- /dev/null +++ b/tools/beatsyncer/tmp/input_audio.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e8e47b4ac39e602e39d2a3c38d4509d11a113f742e0fbe594810468c0507577 +size 406090 diff --git a/tools/image_background_remover/__init__.py b/tools/image_background_remover/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..4dd1cb1145c8a4910c8b71990bf16ef93863b4ac --- /dev/null +++ b/tools/image_background_remover/__init__.py @@ -0,0 +1 @@ +# Image Background Remover package diff --git a/tools/image_background_remover/__pycache__/__init__.cpython-311.pyc b/tools/image_background_remover/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f7d0aba83c085f95624aceb00aad99b237fda7f Binary files /dev/null and b/tools/image_background_remover/__pycache__/__init__.cpython-311.pyc differ diff --git a/tools/image_background_remover/__pycache__/__init__.cpython-312.pyc b/tools/image_background_remover/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ced405ddd57b0549092a6bdcf0057893c0ff76d Binary files /dev/null and b/tools/image_background_remover/__pycache__/__init__.cpython-312.pyc differ diff --git a/tools/image_background_remover/__pycache__/image_background_remover_tool.cpython-311.pyc b/tools/image_background_remover/__pycache__/image_background_remover_tool.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1dd7544719de8a12b24ba3467e51ef4b01c3848 Binary files /dev/null and b/tools/image_background_remover/__pycache__/image_background_remover_tool.cpython-311.pyc differ diff --git a/tools/image_background_remover/__pycache__/image_background_remover_tool.cpython-312.pyc b/tools/image_background_remover/__pycache__/image_background_remover_tool.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ae18ddac61f09e3443b4e8ef91ac0eadd8765f3 Binary files /dev/null and b/tools/image_background_remover/__pycache__/image_background_remover_tool.cpython-312.pyc differ diff --git a/tools/image_background_remover/__pycache__/image_background_remover_ui.cpython-311.pyc b/tools/image_background_remover/__pycache__/image_background_remover_ui.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e0579b9a83ae7ec3f4e2845d6ae7e03de7aee4b Binary files /dev/null and b/tools/image_background_remover/__pycache__/image_background_remover_ui.cpython-311.pyc differ diff --git a/tools/image_background_remover/__pycache__/image_background_remover_ui.cpython-312.pyc b/tools/image_background_remover/__pycache__/image_background_remover_ui.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94bf1abd4dc745a347de4196bf7a305f9176dd53 Binary files /dev/null and b/tools/image_background_remover/__pycache__/image_background_remover_ui.cpython-312.pyc differ diff --git a/tools/image_background_remover/image_background_remover_tool.py b/tools/image_background_remover/image_background_remover_tool.py new file mode 100644 index 0000000000000000000000000000000000000000..1981cf203ccba5ac0dec859b260930d5cb583e92 --- /dev/null +++ b/tools/image_background_remover/image_background_remover_tool.py @@ -0,0 +1,45 @@ +# Image Background Remover Tool +# Version: 1.0.0 +# Author: Isaac + +from rembg import remove +from PIL import Image +import os +import tempfile + +class ImageBackgroundRemover: + """ + A tool that removes the background from images using the rembg library. + """ + + def __init__(self): + self.name = "Image Background Remover" + self.version = "1.0.0" + + def process(self, input_image): + """ + Process the input image and remove its background. + + Args: + input_image: The input image file + + Returns: + Path to the processed image with background removed + """ + try: + # Create a temporary directory to store the output + temp_dir = tempfile.mkdtemp() + output_path = os.path.join(temp_dir, "output.png") + + # Open the input image + img = Image.open(input_image) + + # Remove the background + output = remove(img) + + # Save the result + output.save(output_path) + + return output_path + except Exception as e: + raise Exception(f"Failed to process image: {str(e)}") diff --git a/tools/image_background_remover/image_background_remover_ui.py b/tools/image_background_remover/image_background_remover_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..8fb770fd7d90a0bf0d702592209f8770897d9ae7 --- /dev/null +++ b/tools/image_background_remover/image_background_remover_ui.py @@ -0,0 +1,55 @@ +# Image Background Remover UI +# Version: 1.0.0 +# Author: Isaac + +import gradio as gr +from tools.image_background_remover.image_background_remover_tool import ImageBackgroundRemover + +class ImageBackgroundRemoverUI: + """ + UI for the Image Background Remover tool. + """ + + def __init__(self): + self.name = "Image Background Remover" + self.tool = ImageBackgroundRemover() + + def process_input(self, input_image): + """Process the input image and return the result""" + try: + result = self.tool.process(input_image) + return result + except Exception as e: + return None + + def create_ui(self): + """Create the Gradio UI for this tool""" + with gr.Column(): + gr.Markdown(f"# {self.name}") + gr.Markdown("Upload an image to remove its background") + + input_image = gr.Image(type="filepath", label="Input Image") + process_btn = gr.Button("Remove Background", variant="primary") + output_image = gr.Image(label="Result") + + process_btn.click( + fn=self.process_input, + inputs=[input_image], + outputs=[output_image] + ) + +def run_app(): + """Run the Gradio app""" + ui = ImageBackgroundRemoverUI() + app = gr.Interface( + fn=ui.process_input, + inputs=gr.Image(type="filepath", label="Input Image"), + outputs=gr.Image(label="Result"), + title=ui.name, + description="Upload an image to remove its background", + examples=["examples/sample1.jpg", "examples/sample2.jpg"] if gr.os.path.exists("examples") else None, + ) + app.launch() + +if __name__ == "__main__": + run_app()