Major Features: - 🖥️ Standalone desktop app (VideoMatcher.app) - double-click to run - 🎨 Black & gold branded UI (Montserrat font, #FFC407 accent) - 📁 Local file browser for master/adaptation folders - ⚡ Fast mode processing (10-20x faster, disables AKAZE/AI Vision) - 🤖 Smart AI Vision fallback (auto-retry when no matches found) - 📊 Real-time progress bars (fingerprinting & matching) - 💾 Local processing (no cloud, no authentication) - 📤 CSV export with master filenames Web Application (Enterprise): - 🌐 Flask web app with Azure AD authentication - 📦 Box.com integration for cloud storage - 🐳 Docker support for deployment - 🔐 JWT validation with httpOnly cookies - 🎯 REST API endpoints Enhancements: - Fixed master filename lookup (was showing "Unknown") - Automatic fingerprint recovery (detects missing files) - Improved CSV format (master file next to adaptation) - Port conflict handling (auto-finds available port) - Environment variable fixes for standalone mode Documentation: - Updated README with standalone app section - Added 10+ guide documents (UI improvements, fingerprint recovery, etc.) - Build instructions with PyInstaller - Comprehensive troubleshooting guide Technical: - PyInstaller build configuration (video_matcher.spec) - Launcher with environment setup (launcher.py) - Mock authentication for standalone mode - Video matcher service layer - Metadata parser and AKAZE video matching 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
227 lines
6.3 KiB
Python
Executable file
227 lines
6.3 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""
|
|
Build script for Video Matcher Standalone Application
|
|
Creates a distributable executable using PyInstaller
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
def check_dependencies():
|
|
"""Check if required build dependencies are installed."""
|
|
print("Checking build dependencies...")
|
|
|
|
try:
|
|
import PyInstaller
|
|
print(f"✓ PyInstaller {PyInstaller.__version__} installed")
|
|
except ImportError:
|
|
print("✗ PyInstaller not found")
|
|
print("\nPlease install PyInstaller:")
|
|
print(" pip install pyinstaller")
|
|
return False
|
|
|
|
# Check if FFmpeg is available
|
|
try:
|
|
result = subprocess.run(['ffmpeg', '-version'],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=5)
|
|
if result.returncode == 0:
|
|
print("✓ FFmpeg is available")
|
|
else:
|
|
print("⚠ FFmpeg check returned non-zero exit code")
|
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
print("⚠ FFmpeg not found in PATH")
|
|
print(" The standalone app will require FFmpeg to be installed on target systems")
|
|
|
|
return True
|
|
|
|
def clean_build():
|
|
"""Clean previous build artifacts."""
|
|
print("\nCleaning previous build artifacts...")
|
|
|
|
dirs_to_clean = ['build', 'dist']
|
|
files_to_clean = ['*.spec~']
|
|
|
|
for dir_name in dirs_to_clean:
|
|
if os.path.exists(dir_name):
|
|
print(f" Removing {dir_name}/")
|
|
shutil.rmtree(dir_name)
|
|
|
|
print("✓ Clean complete")
|
|
|
|
def build_application():
|
|
"""Build the application using PyInstaller."""
|
|
print("\nBuilding application...")
|
|
print("=" * 60)
|
|
|
|
try:
|
|
# Run PyInstaller with the spec file
|
|
result = subprocess.run(
|
|
['pyinstaller', 'video_matcher.spec', '--clean'],
|
|
check=True
|
|
)
|
|
|
|
if result.returncode == 0:
|
|
print("=" * 60)
|
|
print("✓ Build successful!")
|
|
return True
|
|
else:
|
|
print("✗ Build failed")
|
|
return False
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"✗ Build failed with error: {e}")
|
|
return False
|
|
except FileNotFoundError:
|
|
print("✗ PyInstaller not found")
|
|
return False
|
|
|
|
def create_distribution():
|
|
"""Create distribution package with data directories."""
|
|
print("\nCreating distribution package...")
|
|
|
|
dist_dir = Path('dist/VideoMatcher')
|
|
|
|
if not dist_dir.exists():
|
|
print("✗ Distribution directory not found")
|
|
return False
|
|
|
|
# Create data directory structure
|
|
data_dir = dist_dir / 'data'
|
|
data_dir.mkdir(exist_ok=True)
|
|
(data_dir / 'fingerprints').mkdir(exist_ok=True)
|
|
(data_dir / 'jobs').mkdir(exist_ok=True)
|
|
|
|
# Create empty masters.json
|
|
masters_file = data_dir / 'masters.json'
|
|
if not masters_file.exists():
|
|
with open(masters_file, 'w') as f:
|
|
f.write('[]')
|
|
|
|
# Create tmp directory
|
|
tmp_dir = dist_dir / 'tmp' / 'video_downloads'
|
|
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Create README
|
|
readme_content = """# Video Matcher - Standalone Application
|
|
|
|
## How to Run
|
|
|
|
1. **macOS**: Double-click `VideoMatcher` (or `VideoMatcher.app`)
|
|
2. **Windows**: Double-click `VideoMatcher.exe`
|
|
3. **Linux**: Run `./VideoMatcher` in terminal
|
|
|
|
The application will:
|
|
- Start a local web server
|
|
- Automatically open your browser
|
|
- Prompt you to select master and adaptation folders
|
|
- Process videos and show results
|
|
|
|
## Requirements
|
|
|
|
- **FFmpeg**: Must be installed on your system
|
|
- macOS: `brew install ffmpeg`
|
|
- Windows: Download from https://ffmpeg.org/download.html
|
|
- Linux: `sudo apt-get install ffmpeg`
|
|
|
|
## Data Storage
|
|
|
|
All data is stored locally in the `data/` folder:
|
|
- `data/masters.json` - Master video registry
|
|
- `data/fingerprints/` - Video fingerprints cache
|
|
- `data/jobs/` - Job history (if any)
|
|
|
|
## Troubleshooting
|
|
|
|
### Application won't start
|
|
- Check that FFmpeg is installed: `ffmpeg -version`
|
|
- Check console output for error messages
|
|
|
|
### Port already in use
|
|
- The app will automatically find an available port
|
|
- If issues persist, close other applications using ports 5000-5010
|
|
|
|
### Permission errors
|
|
- On macOS, you may need to allow the app in System Preferences > Security
|
|
- On Windows, you may need to allow through Windows Defender
|
|
|
|
## Support
|
|
|
|
For issues or questions, contact your system administrator.
|
|
"""
|
|
|
|
readme_file = dist_dir / 'README.txt'
|
|
with open(readme_file, 'w') as f:
|
|
f.write(readme_content)
|
|
|
|
print("✓ Distribution package created")
|
|
print(f"\nApplication location: {dist_dir}")
|
|
|
|
return True
|
|
|
|
def show_next_steps():
|
|
"""Show instructions for next steps."""
|
|
print("\n" + "=" * 60)
|
|
print("BUILD COMPLETE!")
|
|
print("=" * 60)
|
|
print("\nYour standalone application is ready:")
|
|
print(" Location: dist/VideoMatcher/")
|
|
print("\nTo distribute:")
|
|
print(" 1. Zip the entire 'VideoMatcher' folder")
|
|
print(" 2. Share the zip file with users")
|
|
print(" 3. Users extract and run the VideoMatcher executable")
|
|
print("\nTo test locally:")
|
|
print(" cd dist/VideoMatcher")
|
|
|
|
if sys.platform == 'darwin':
|
|
print(" ./VideoMatcher")
|
|
print(" or: open VideoMatcher.app")
|
|
elif sys.platform == 'win32':
|
|
print(" VideoMatcher.exe")
|
|
else:
|
|
print(" ./VideoMatcher")
|
|
|
|
print("\n" + "=" * 60)
|
|
|
|
def main():
|
|
"""Main build process."""
|
|
print("=" * 60)
|
|
print(" VIDEO MATCHER - Standalone Build Script")
|
|
print("=" * 60)
|
|
|
|
# Check dependencies
|
|
if not check_dependencies():
|
|
print("\n✗ Build cancelled: Missing dependencies")
|
|
sys.exit(1)
|
|
|
|
# Clean previous builds
|
|
clean_build()
|
|
|
|
# Build application
|
|
if not build_application():
|
|
print("\n✗ Build cancelled: Build failed")
|
|
sys.exit(1)
|
|
|
|
# Create distribution
|
|
if not create_distribution():
|
|
print("\n✗ Build cancelled: Distribution creation failed")
|
|
sys.exit(1)
|
|
|
|
# Show next steps
|
|
show_next_steps()
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
main()
|
|
except KeyboardInterrupt:
|
|
print("\n\n✗ Build cancelled by user")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"\n✗ Build failed with error: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|