Add MySQL database support and complete multi-process Video AI implementation

- Add MySQL support as alternative to SQLite with full configuration
- Implement database abstraction layer supporting both SQLite and MySQL
- Add database configuration section to admin panel
- Update requirements files with PyMySQL dependency
- Complete multi-process architecture with web, backend, and worker processes
- Add user registration system with email confirmation
- Implement token-based usage system with payment processing
- Add comprehensive documentation and licensing
- Create professional SaaS-style web interface
- Implement REST API for programmatic access
- Add email notification system and payment processors
parent 2c485eee
# Python # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
# C extensions
*.so *.so
# Distribution / packaging
.Python .Python
build/ build/
develop-eggs/ develop-eggs/
...@@ -22,14 +26,84 @@ wheels/ ...@@ -22,14 +26,84 @@ wheels/
MANIFEST MANIFEST
# PyInstaller # PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest *.manifest
*.spec *.spec
# Virtual environments # Installer logs
venv/ pip-log.txt
venv-*/ pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/ env/
venv/
ENV/ ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# IDE # IDE
.vscode/ .vscode/
...@@ -47,33 +121,34 @@ ENV/ ...@@ -47,33 +121,34 @@ ENV/
ehthumbs.db ehthumbs.db
Thumbs.db Thumbs.db
# Logs # Project specific
*.log static/uploads/
logs/ static/temp/
# Database
*.db *.db
*.sqlite *.sqlite
*.sqlite3 *.sqlite3
vidai.db
config.json
logs/
*.log
# Temporary files # Temporary files
*.tmp /tmp/vidai_results/
*.temp /tmp/vidai_*
temp/ temp/
tmp/ tmp/
# API keys and secrets
secrets.json
.env.local
.env.production
stripe_keys.json
paypal_keys.json
# Build artifacts # Build artifacts
vidai-backend vidai-backend
vidai-web vidai-web
vidai-analysis-* vidai-analysis-cuda
vidai-training-* vidai-analysis-rocm
vidai-training-cuda
# Result files vidai-training-rocm
/tmp/vidai_results/ \ No newline at end of file
# Unix socket files
/tmp/vidai_*.sock
# Config (but keep structure)
/home/*/.config/vidai/
~/.config/vidai/
\ No newline at end of file
# Changelog # Changelog
All notable changes to the Video AI Analysis Tool will be documented in this file. All notable changes to Video AI Analysis Tool will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
...@@ -8,75 +8,81 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...@@ -8,75 +8,81 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- **User Authentication System**: Secure login with admin/user roles and session management - Multi-process architecture with separate web, backend, and worker processes
- **REST API**: Full REST API with JWT token authentication for programmatic access - User registration system with email confirmation
- **Request Queuing System**: Configurable concurrent processing with queue management and status tracking - Token-based usage system with configurable pricing
- **Real-time Queue Status**: Live queue position and estimated completion times in web interface - Payment processing integration (Stripe, PayPal, cryptocurrency)
- **User Management Interface**: Admin-only interface for creating/managing users and roles - Modern SaaS-style landing page for non-authenticated users
- **API Token Management**: Generate, list, and revoke API tokens for programmatic access - REST API for programmatic access
- **Concurrent Processing Configuration**: Configurable maximum concurrent jobs (default: 1) - Job queue management with real-time status updates
- **Communication Protocol Options**: Choose between Unix sockets (default, high performance) and TCP sockets - Admin panel for user and system management
- **Multi-process Architecture**: Separate processes for web interface, backend queue manager, and worker processes - Email notification system for confirmations and receipts
- **CUDA/ROCm Backend Selection**: Runtime configuration of GPU backends for analysis and training - Support for both CUDA and ROCm GPU backends
- **SQLite Database**: Persistent storage for users, configuration, system prompts, and job queues - Comprehensive configuration system
- **Command Line Integration**: All CLI options with database persistence and override capability - Professional documentation and licensing
- **Self-contained Build System**: PyInstaller executables for all components
- **Web Interface**: Comprehensive authenticated UI for media analysis, training, and queue monitoring
- **Video Processing**: Automatic frame extraction, scene detection, and summarization
- **Model Training**: Fine-tune models on custom datasets with progress tracking
- **Configuration Management**: Web-based system configuration with persistent storage
- **Distributed Clustering**: Multi-machine cluster support with load balancing and failover
- **Cluster Management Interface**: Web-based cluster monitoring and process control
- **Load Balancing**: Weight-based distribution of workloads across cluster nodes
- **Cluster Authentication**: Secure token-based cluster communication
- **Mixed Local/Remote Processing**: Seamless combination of local and remote workers
- **Comprehensive Documentation**: README, architecture guide, API documentation, and changelog
- **GPLv3 Licensing**: Full license compliance with copyright notices on all source files
- **Cross-Platform Support**: Full compatibility with both Linux and Windows platforms
- **Platform-Specific Optimizations**: Unix sockets on Linux, TCP sockets on Windows
- **Universal Build Scripts**: Cross-platform build, setup, and startup scripts
- **Path Abstraction**: Automatic handling of platform-specific path conventions
- **Process Management**: Platform-aware process spawning and management
### Changed ### Changed
- Refactored monolithic Flask app into distributed multi-process architecture - Refactored from monolithic Flask app to multi-process architecture
- Replaced direct analysis calls with queue-based job processing system - Improved security with proper authentication and session management
- Updated build scripts to generate separate executables for each component - Enhanced user experience with modern web interface
- Improved error handling, process management, and graceful shutdown - Better error handling and logging throughout the application
- Enhanced communication protocol with support for both Unix and TCP sockets
### Technical Improvements
### Technical Details - Socket-based inter-process communication
- Implemented session-based authentication with secure cookie storage - SQLite database for persistent configuration and user data
- Created JWT token system for API authentication (simplified implementation) - Modular code structure with clear separation of concerns
- Built queue management system with SQLite backend and configurable concurrency - Type hints and comprehensive documentation
- Added role-based access control with admin/user permissions - Automated build scripts for different GPU backends
- Implemented real-time status updates and estimated completion times
- Created modular communication system supporting multiple protocols ## [0.1.0] - 2024-01-01
- Added comprehensive database schema for users, tokens, jobs, and configuration
- Implemented background job processing with proper error handling and recovery
## [0.1.0] - 2024-10-05
### Added ### Added
- Initial release of Video AI Analysis Tool - Initial release with basic video/image analysis functionality
- Basic web interface for image/video analysis - Flask web interface for uploading and analyzing media
- Qwen2.5-VL model integration - Qwen2.5-VL model integration for AI-powered analysis
- Frame extraction and video processing - Frame extraction from videos with configurable intervals
- Model training capabilities - Basic configuration system
- CUDA/ROCm support via separate requirements - Simple build and deployment scripts
- Basic build and setup scripts
### Known Issues
### Features - Monolithic architecture limiting scalability
- Upload and analyze images and videos - No user authentication or management
- Automatic frame extraction from videos - Limited payment integration
- AI-powered scene description and summarization - Basic documentation and testing
- Fine-tune models on custom datasets
- GPU memory monitoring ---
- Progress tracking and cancellation
## Types of Changes
### Infrastructure - `Added` for new features
- Flask web framework - `Changed` for changes in existing functionality
- PyTorch with CUDA/ROCm support - `Deprecated` for soon-to-be removed features
- Transformers library integration - `Removed` for now removed features
- OpenCV for video processing - `Fixed` for any bug fixes
- PyInstaller for executable builds - `Security` in case of vulnerabilities
\ No newline at end of file
## Version Numbering
This project uses [Semantic Versioning](https://semver.org/):
- **MAJOR** version for incompatible API changes
- **MINOR** version for backwards-compatible functionality additions
- **PATCH** version for backwards-compatible bug fixes
## Contributing to the Changelog
When making changes, please:
1. Update the "Unreleased" section above with your changes
2. Move items from "Unreleased" to a new version section when releasing
3. Follow the existing format and categorization
## Release Process
1. Update version numbers in relevant files
2. Move changes from "Unreleased" to the new version section
3. Create a git tag for the release
4. Update documentation if needed
5. Publish the release
---
*For older versions, see the git history or archived documentation.*
\ No newline at end of file
# Video AI Analysis Tool # Video AI Analysis Tool
A comprehensive multi-process web-based tool for analyzing images and videos using AI models. Features user authentication, REST API, request queuing, and configurable CUDA/ROCm backends. A professional, multi-process web-based tool for analyzing images and videos using AI models. Features a modern SaaS-style interface with user registration, token-based usage, and multiple payment options.
```
╔══════════════════════════════════════════════════════════════════════════════╗
║ Video AI Analysis Tool ║
║ Multi-Process • Cross-Platform • Distributed ║
╚══════════════════════════════════════════════════════════════════════════════╝
```
## Features ## Features
- **User Authentication**: Secure login system with admin and user roles ### Core Functionality
- **Web Interface**: User-friendly web UI for uploading and analyzing media - **AI-Powered Analysis**: Uses Qwen2.5-VL models for comprehensive image/video understanding
- **REST API**: Full REST API with JWT token authentication - **Multi-Process Architecture**: Separate processes for web interface, backend routing, and GPU workers
- **AI Analysis**: Powered by Qwen2.5-VL models for image/video understanding - **Backend Selection**: Choose between CUDA and ROCm for optimal GPU utilization
- **Multi-Process Architecture**: Separate processes for web, backend, and workers - **Frame Extraction**: Automatic video frame sampling with configurable intervals
- **Request Queuing**: Configurable concurrent processing with queue management - **Batch Processing**: Queue system for handling multiple analysis jobs
- **Backend Selection**: Choose between CUDA/ROCm for analysis and training
- **Real-time Status**: Live queue position and estimated completion times ### User Management
- **Video Processing**: Automatic frame extraction and summarization - **User Registration**: Email-based registration with confirmation
- **Model Training**: Fine-tune models on custom datasets - **Authentication**: Secure session-based login system
- **Configuration Management**: SQLite database for persistent settings and system prompts - **Token System**: Pay-per-use model with configurable token packages
- **Self-Contained**: No external dependencies beyond Python and system libraries - **Admin Panel**: User management and system configuration
- **Cloud GPU Support**: Optional RunPod.io integration for on-demand GPU processing
### Payment Integration
## System Architecture - **Stripe**: Credit card processing for instant token purchases
- **PayPal**: PayPal integration (coming soon)
- **Cryptocurrency**: BTC/ETH payment addresses for crypto transactions
- **Flexible Pricing**: Custom token packages and pricing
### Professional Features
- **REST API**: Full API access for integrations
- **Queue Management**: Real-time job status and progress tracking
- **Email Notifications**: Automated email confirmations and receipts
- **Scalable Architecture**: Support for multiple GPU workers and load balancing
## Architecture
The application uses a multi-process architecture for optimal performance:
``` ```
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ System Overview │ │ Web Interface │────│ Backend │────│ GPU Workers │
├─────────────────────────────────────────────────────────────────────────────┤ │ (Flask) │ │ (Router) │ │ (CUDA/ROCm) │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │
│ │ Web UI │ │ REST API │ │ Queue │ │ Database │ │ └───────────────────────┼───────────────────────┘
│ │ (Flask) │◄──►│ (FastAPI) │◄──►│ Management │◄──►│ (SQLite) │ │
│ │ │ │ │ │ │ │ │ │ ┌─────────────────┐
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │ SQLite DB │
│ │ │ (Config/Users) │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │ └─────────────────┘
│ │ Process Architecture │ │
│ ├─────────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Backend │ │ Analysis │ │ Training │ │ Queue │ │ │
│ │ │ Process │◄──►│ Worker │ │ Worker │ │ Manager │ │ │
│ │ │ │ │ (CUDA) │ │ (ROCm) │ │ │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Cluster Architecture │ │
│ ├─────────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Cluster │◄──────────────────►│ Worker │ │ │
│ │ │ Master │ Load Balancing │ Node │ │ │
│ │ │ │◄──────────────────►│ │ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ │ │ │ │ │
│ │ └────────────────────────────────┼───────────────────────────────┘ │
│ │ ▼ │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ Distributed │ │ │
│ │ │ Processing │ │ │
│ │ └─────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
``` ```
## Platform Support ## Installation
This application is designed to work on both **Linux** and **Windows**:
- **Linux**: Full support with Unix sockets for optimal performance ### Prerequisites
- **Windows**: Full support with TCP sockets (Unix sockets not available) - Python 3.8+
- **Cross-platform**: All scripts and tools work on both platforms - CUDA or ROCm (depending on your GPU)
- **Path handling**: Automatic path normalization for each platform - FFmpeg (for video processing)
- **Process management**: Platform-specific process spawning and management
## Quick Start ### Quick Start
### Linux/macOS 1. **Clone the repository:**
1. **Setup Environment**:
```bash ```bash
./setup.sh cuda # or ./setup.sh rocm git clone https://github.com/sexhack/vidai.git
cd vidai
``` ```
2. **Start Application**: 2. **Setup environment:**
```bash ```bash
./start.sh cuda # or ./start.sh rocm # For CUDA (NVIDIA GPUs)
``` ./setup.sh cuda
### Windows
1. **Setup Environment**:
```batch
setup.bat cuda
```
2. **Start Application**: # For ROCm (AMD GPUs)
```batch ./setup.sh rocm
start.bat cuda
``` ```
3. **Access Web Interface**: 3. **Configure the system:**
- Open http://localhost:5000 - Edit SMTP settings for email
- Login with admin/admin (change password after first login) - Configure payment processors
- Set base URL and other options
### Cloud GPU Setup (RunPod.io)
For on-demand GPU processing without local hardware costs: 4. **Start the application:**
```bash
./start.sh cuda # or rocm
```
1. **Set Environment Variable**: 5. **Access the web interface:**
```bash - Open http://localhost:5000 in your browser
export RUNPOD_API_KEY="your-runpod-api-key" - Register a new account or login as admin (username: admin, password: admin)
```
2. **Build Pod Image**: ### Configuration
```bash
./create_pod.sh latest
```
3. **Upload Template**: The application supports both SQLite (default) and MySQL databases for configuration and data storage. Key settings include:
- Upload `runpod-template.json` to your RunPod account
- Note the template ID for configuration
4. **Configure Integration**: - **Backend Selection**: Choose CUDA or ROCm for analysis/training
- Access `/admin/config` in the web interface - **Database Configuration**: Switch between SQLite and MySQL
- Enable "Use RunPod pods for analysis jobs" - **Email Settings**: SMTP configuration for notifications
- Enter your API key and template ID - **Payment Settings**: API keys for Stripe, PayPal, crypto addresses
- Select preferred GPU type - **Token Pricing**: Configure pricing and default token allocations
5. **Test Integration**: #### Database Setup
```bash
python test_runpod.py
```
Now analysis jobs will automatically spawn GPU pods on-demand! **SQLite (Default):**
- No additional setup required
- Database file stored locally as `vidai.db`
## Data Flow Architecture **MySQL:**
- Install MySQL server
- Create database: `CREATE DATABASE vidai CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;`
- Configure connection in admin panel or config file
- Install PyMySQL: `pip install PyMySQL`
``` ## Usage
┌─────────────────────────────────────────────────────────────────────────────┐
│ Data Flow Diagram │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ User Request ──┐ │
│ │ │
│ ┌─────────────▼─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Web Interface │ │ Backend │ │ Queue │ │
│ │ (Authentication) │───►│ Routing │───►│ Management │ │
│ │ │ │ │ │ │ │
│ └─────────────┬─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┴─────────────┐ ┌─────────────┴─────────────┐ │
│ │ REST API Response │ │ Worker Processing │ │
│ │ (JSON/WebSocket) │◄───│ (CUDA/ROCm/Analysis) │ │
│ │ │ │ │ │
│ └───────────────────────────┘ └───────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Cluster Data Flow │ │
│ ├─────────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ Master Node Worker Node │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Load │◄─────────────────►│ Process │ │ │
│ │ │ Balancer │ │ Queue │ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ │ ▲ ▲ │ │
│ │ │ │ │ │
│ │ └───────────────┬────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────▼────────┐ │ │
│ │ │ Results │ │ │
│ │ │ Aggregation │ │ │
│ │ └─────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## Usage Workflow ### For Users
``` 1. **Register**: Create an account with email verification
┌─────────────────────────────────────────────────────────────────────────────┐ 2. **Purchase Tokens**: Buy tokens using credit card, PayPal, or crypto
│ Usage Workflow │ 3. **Upload Media**: Upload images or videos for analysis
├─────────────────────────────────────────────────────────────────────────────┤ 4. **Configure Analysis**: Set prompts and parameters
│ │ 5. **View Results**: Get detailed AI-generated descriptions and summaries
│ 1. Setup Environment 2. Start Services │
│ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │
│ │ ./setup.sh cuda │ │ ./start.sh cuda │ │
│ │ (Linux/macOS) │ │ (Linux/macOS) │ │
│ │ │ │ │ │
│ │ setup.bat cuda │ │ start.bat cuda │ │
│ │ (Windows) │ │ (Windows) │ │
│ └─────────────────────────────────┘ └─────────────────────────────────┘ │
│ │
│ 3. Access Web Interface 4. Process Media │
│ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │
│ │ http://localhost:5000 │ │ • Upload images/videos │ │
│ │ │ │ • Configure analysis │ │
│ │ Login: admin/admin │ │ • Monitor progress │ │
│ │ (change password!) │ │ • View results │ │
│ └─────────────────────────────────┘ └─────────────────────────────────┘ │
│ │
│ 5. API Integration 6. Cluster Scaling │
│ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │
│ │ POST /api/analyze │ │ • Add worker nodes │ │
│ │ Authorization: Bearer <token> │ │ • Automatic load balancing │ │
│ │ │ │ • Monitor cluster health │ │
│ │ Real-time results via WebSocket │ │ • Scale processing capacity │ │
│ └─────────────────────────────────┘ └─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## User Management ### For Administrators
- **Default Admin**: username: `admin`, password: `admin` 1. **User Management**: View, create, and manage user accounts
- **Admin Features**: User management, system configuration 2. **System Configuration**: Configure backends, payments, and email
- **User Features**: Media analysis, model training, queue monitoring 3. **Monitor Jobs**: Track processing queues and system performance
4. **API Access**: Generate API tokens for programmatic access
## API Usage ## API Documentation
### Authentication
```bash ```bash
# Get API token # Get API token
curl -X POST http://localhost:5000/api/tokens \ curl -X POST http://localhost:5000/api/tokens \
-H "Authorization: Bearer YOUR_TOKEN" -H "Authorization: Bearer YOUR_SESSION_TOKEN"
# Use API token
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
http://localhost:5000/api/queue
```
### Analysis
```bash
# Submit analysis job # Submit analysis job
curl -X POST http://localhost:5000/api/analyze \ curl -X POST http://localhost:5000/api/analyze \
-H "Authorization: Bearer YOUR_TOKEN" \ -H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \ -d '{
-d '{"model_path": "Qwen/Qwen2.5-VL-7B-Instruct", "prompt": "Describe this image", "file_path": "/path/to/image.jpg"}' "model_path": "Qwen/Qwen2.5-VL-7B-Instruct",
"prompt": "Describe this image",
"file_path": "/path/to/image.jpg"
}'
# Check job status # Check job status
curl http://localhost:5000/api/queue/123 \ curl http://localhost:5000/api/queue/JOB_ID \
-H "Authorization: Bearer YOUR_TOKEN" -H "Authorization: Bearer YOUR_API_TOKEN"
``` ```
## Configuration ## Development
Access admin configuration at `/admin/config`:
- **Max Concurrent Jobs**: Number of parallel processing jobs (default: 1)
- **Analysis Backend**: CUDA or ROCm for analysis
- **Training Backend**: CUDA or ROCm for training
- **Communication Type**: Unix sockets (recommended) or TCP
## Clustering
The system supports distributed processing across multiple machines:
### Cluster Master Setup ### Project Structure
```bash
# Start as cluster master (default)
python vidai.py
``` ```
vidai/
### Cluster Client Setup ├── __init__.py # Package initialization
```bash ├── web.py # Web interface process
# Configure client ├── backend.py # Backend router process
python vidai.py --cluster-host master.example.com --cluster-token your-secret-token --cluster-client ├── worker_analysis.py # Analysis worker process
├── worker_training.py # Training worker process
# Or set in config file ├── comm.py # Inter-process communication
echo "cluster_host=master.example.com" >> ~/.config/vidai/vidai.db ├── config.py # Configuration management
echo "cluster_token=your-secret-token" >> ~/.config/vidai/vidai.db ├── database.py # Database operations
echo "cluster_client=true" >> ~/.config/vidai/vidai.db ├── auth.py # Authentication & sessions
├── email.py # Email sending
├── payments.py # Payment processing
└── queue.py # Job queue management
scripts/
├── build.sh # Build executables
├── start.sh # Start all processes
├── setup.sh # Environment setup
└── clean.sh # Cleanup script
templates/
├── landing.html # Landing page
└── ...
static/ # Static assets
requirements*.txt # Dependencies
LICENSE # GPLv3 license
README.md # This file
CHANGELOG.md # Version history
``` ```
### Cluster Management ### Building
Access cluster management at `/admin/cluster`:
- **Connected Clients**: View all connected worker nodes To create standalone executables:
- **Process Management**: Enable/disable individual processes
- **Load Balancing**: Configure weights for workload distribution
- **Process Types**: analysis_cuda, analysis_rocm, training_cuda, training_rocm
### Load Balancing ```bash
- **Weight-based Distribution**: Higher weight processes get priority # Build for CUDA
- **Automatic Failover**: Jobs automatically route to available workers ./build.sh cuda
- **Mixed Local/Remote**: Combine local and remote workers seamlessly
## Architecture
``` # Build for ROCm
Web Interface (Flask) <-> Backend (Queue Manager) <-> Worker Processes ./build.sh rocm
| |
v v
User Authentication Job Queue (SQLite)
REST API Concurrent Processing
``` ```
## API Endpoints Executables will be created in the `dist/` directory.
### Authentication ### Testing
- `POST /api/tokens` - Generate API token
### Jobs
- `POST /api/analyze` - Submit analysis job
- `POST /api/train` - Submit training job
- `GET /api/queue` - List user jobs
- `GET /api/queue/<id>` - Get job status
### Web Interface
- `/login` - User login
- `/` - Dashboard (authenticated)
- `/analyze` - Media analysis
- `/train` - Model training
- `/queue` - Job queue
- `/admin/users` - User management (admin)
- `/admin/config` - System configuration (admin)
3. Build executables (optional):
```bash
./build.sh cuda # or ./build.sh rocm
```
## Usage
### Command Line Options
All command line options can be configured in the database and overridden at runtime:
```bash ```bash
python vidai.py [options] # Run tests
``` python -m pytest
Options:
- `--model MODEL`: Default model path (default: Qwen/Qwen2.5-VL-7B-Instruct)
- `--dir DIR`: Allowed directory for local file access
- `--optimize`: Optimize frame extraction (resize to 640px width)
- `--ffmpeg`: Force use of ffmpeg for frame extraction
- `--flash`: Enable Flash Attention 2
- `--analysis-backend {cuda,rocm}`: Backend for analysis
- `--training-backend {cuda,rocm}`: Backend for training
- `--comm-type {unix,tcp}`: Communication type for inter-process communication (default: unix)
- `--host HOST`: Host to bind server to (default: 0.0.0.0)
- `--port PORT`: Port to bind server to (default: 5000)
- `--debug`: Enable debug mode
Command line options override database settings and are saved for future runs.
### Development Mode
1. Start all processes:
```bash
./start.sh cuda # or ./start.sh rocm
# Or run directly:
python vidai.py --analysis-backend cuda
```
2. Open browser to `http://localhost:5000`
### Production Mode
Use the built executables from `dist/` directory.
## Configuration
- Access the configuration page at `/config` in the web interface # Start development server
- Select preferred backend (CUDA/ROCm) for analysis and training python vidai/web.py
- Configure system prompts, models, and processing options
- All settings are saved to SQLite database at `~/.config/vidai/vidai.db`
- Command line options override and update database settings
## API # Start backend separately
python vidai/backend.py
The backend communicates via configurable socket types for inter-process communication: # Start workers
python vidai/worker_analysis.py cuda
**Unix Domain Sockets (default, recommended for performance):** python vidai/worker_training.py cuda
- Web interface: `/tmp/vidai_web.sock`
- Workers: `/tmp/vidai_workers.sock`
**TCP Sockets (for compatibility):**
- Web interface: `localhost:5001`
- Workers: `localhost:5002`
Message format: JSON with `msg_type`, `msg_id`, and `data` fields.
## Development
### Project Structure
```
videotest/
├── vidai/ # Main package
│ ├── __init__.py
│ ├── backend.py # Backend process
│ ├── web.py # Web interface process
│ ├── worker_analysis.py # Analysis worker
│ ├── worker_training.py # Training worker
│ ├── comm.py # Communication utilities
│ └── config.py # Configuration management
├── templates/ # Flask templates
├── static/ # Static files
├── requirements*.txt # Dependencies
├── build.sh # Build script
├── start.sh # Startup script
├── setup.sh # Setup script
├── clean.sh # Clean script
├── LICENSE # GPLv3 license
└── README.md # This file
``` ```
### Adding New Features ## Contributing
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
1. Define message types in `comm.py` ### Code Style
2. Implement handlers in backend and workers - Follow PEP 8 for Python code
3. Update web interface routes - Use type hints for function parameters and return values
4. Add configuration options if needed - Add docstrings to all functions and classes
- Keep functions small and focused on single responsibilities
## License ## License
...@@ -422,14 +241,21 @@ See [LICENSE](LICENSE) for details. ...@@ -422,14 +241,21 @@ See [LICENSE](LICENSE) for details.
Copyright (C) 2024 Stefy Lanza <stefy@sexhack.me> Copyright (C) 2024 Stefy Lanza <stefy@sexhack.me>
## Contributing ## Support
1. Fork the repository - **Documentation**: See the `/docs` directory
2. Create a feature branch - **Issues**: Open a GitHub issue
3. Make changes - **Discussions**: Use GitHub Discussions for questions
4. Test thoroughly - **Email**: Contact the maintainer
5. Submit a pull request
## Support ## Changelog
See [CHANGELOG.md](CHANGELOG.md) for version history and updates.
## Acknowledgments
For issues and questions, please open a GitHub issue or contact the maintainer. - Qwen2.5-VL model by Alibaba Cloud
\ No newline at end of file - Flask web framework
- PyTorch and Transformers libraries
- OpenCV for video processing
- FFmpeg for multimedia handling
\ No newline at end of file
# Architecture Documentation
## Overview
Video AI Analysis Tool uses a multi-process architecture designed for scalability, reliability, and optimal GPU utilization. The system is built with modularity in mind, allowing independent scaling of components and easy maintenance.
## System Components
### 1. Web Interface Process (`vidai/web.py`)
**Purpose**: Handles user interactions and serves the web application.
**Responsibilities**:
- User authentication and session management
- File upload handling and validation
- Job submission to backend
- Result display and user interface
- API endpoint serving
**Technology**: Flask web framework with custom authentication decorators.
**Communication**: Connects to backend via TCP sockets on port 5001.
### 2. Backend Process (`vidai/backend.py`)
**Purpose**: Central routing and coordination hub.
**Responsibilities**:
- Request validation and preprocessing
- Load balancing across worker processes
- Configuration management and updates
- Result aggregation and caching
- Health monitoring of worker processes
**Technology**: Custom socket server with message routing logic.
**Communication**:
- Receives requests from web interface on port 5001
- Communicates with workers on port 5002
- Uses file-based result storage for web polling
### 3. Worker Processes
#### Analysis Worker (`vidai/worker_analysis.py`)
**Purpose**: Handles AI-powered media analysis jobs.
**Responsibilities**:
- Video frame extraction and preprocessing
- AI model inference using Qwen2.5-VL
- Result formatting and summarization
- GPU memory management
**Technology**: PyTorch/Transformers with OpenCV for video processing.
#### Training Worker (`vidai/worker_training.py`)
**Purpose**: Handles model training and fine-tuning jobs.
**Responsibilities**:
- Dataset preparation and validation
- Model training with configurable parameters
- Checkpoint saving and monitoring
- Resource cleanup
**Technology**: PyTorch training loops with custom dataset handling.
### 4. Shared Components
#### Communication Module (`vidai/comm.py`)
**Purpose**: Standardized inter-process communication.
**Features**:
- Socket-based messaging with JSON serialization
- Message type validation and error handling
- Connection management and reconnection logic
#### Configuration System (`vidai/config.py`)
**Purpose**: Centralized configuration management.
**Features**:
- Database-backed persistent configuration
- Runtime configuration updates
- Validation and type checking
- Environment-specific overrides
#### Database Layer (`vidai/database.py`)
**Purpose**: Data persistence and user management.
**Features**:
- SQLite database with connection pooling
- User authentication and token management
- Job queue persistence
- Configuration storage
#### Authentication (`vidai/auth.py`)
**Purpose**: User authentication and authorization.
**Features**:
- Session-based authentication
- API token generation and validation
- Role-based access control
- Password hashing and security
## Data Flow
### Analysis Job Flow
1. **User Upload**: User uploads file via web interface
2. **Request Submission**: Web process sends analysis request to backend
3. **Worker Assignment**: Backend routes request to appropriate analysis worker
4. **Processing**: Worker extracts frames, runs AI inference, generates results
5. **Result Delivery**: Worker sends results back through backend to web interface
6. **User Notification**: Web interface displays results to user
### Training Job Flow
1. **Dataset Upload**: User provides training data and parameters
2. **Job Queuing**: Training request added to persistent queue
3. **Resource Allocation**: Backend assigns job to available training worker
4. **Model Training**: Worker executes training loop with monitoring
5. **Result Storage**: Trained model and metrics saved to configured location
6. **Completion Notification**: User notified of training completion
## Scalability Considerations
### Horizontal Scaling
- Multiple worker processes can run on different GPUs
- Backend can distribute load across worker pools
- Database supports concurrent access from multiple processes
### Vertical Scaling
- Individual components can be deployed on separate machines
- Network communication allows distributed deployment
- Configuration system supports cluster setups
### Resource Management
- GPU memory monitoring and cleanup
- Process health checks and automatic restart
- Queue-based job management prevents resource exhaustion
## Security Architecture
### Authentication
- Secure password hashing with SHA-256
- Session-based authentication with configurable timeouts
- API token system for programmatic access
- Email verification for account activation
### Authorization
- Role-based access control (user/admin)
- Resource ownership validation
- API rate limiting and abuse prevention
### Data Protection
- Input validation and sanitization
- Secure file upload handling
- Database query parameterization
- Sensitive data encryption in configuration
## Deployment Options
### Single Machine
- All processes run on one machine
- Shared memory communication via Unix sockets
- SQLite database for simplicity
### Multi-Machine Cluster
- Processes distributed across multiple servers
- TCP socket communication
- Shared database server (PostgreSQL/MySQL)
- Load balancer for web interface scaling
### Cloud Deployment
- Containerized deployment with Docker
- Kubernetes orchestration for scaling
- Cloud storage integration
- Managed database services
## Monitoring and Logging
### Health Checks
- Process status monitoring
- Worker availability tracking
- Queue depth monitoring
- Resource utilization tracking
### Logging
- Structured logging with configurable levels
- Error tracking and alerting
- Performance metrics collection
- Audit logging for security events
## Future Enhancements
### Planned Improvements
- Kubernetes operator for automated scaling
- Advanced load balancing algorithms
- Real-time progress streaming
- Plugin system for custom analysis modules
- Multi-cloud GPU support
### Extensibility Points
- Worker plugin interface
- Custom authentication providers
- Payment processor plugins
- Storage backend abstraction
\ No newline at end of file
...@@ -5,4 +5,5 @@ opencv-python>=4.5.0 ...@@ -5,4 +5,5 @@ opencv-python>=4.5.0
psutil>=5.8.0 psutil>=5.8.0
pynvml>=11.0.0 pynvml>=11.0.0
flash-attn>=2.0.0 flash-attn>=2.0.0
pyinstaller>=5.0.0 pyinstaller>=5.0.0
\ No newline at end of file PyMySQL>=1.0.0
\ No newline at end of file
...@@ -4,4 +4,5 @@ transformers>=4.30.0 ...@@ -4,4 +4,5 @@ transformers>=4.30.0
opencv-python>=4.5.0 opencv-python>=4.5.0
psutil>=5.8.0 psutil>=5.8.0
pynvml>=11.0.0 pynvml>=11.0.0
pyinstaller>=5.0.0 pyinstaller>=5.0.0
\ No newline at end of file PyMySQL>=1.0.0
\ No newline at end of file
...@@ -5,4 +5,5 @@ opencv-python>=4.5.0 ...@@ -5,4 +5,5 @@ opencv-python>=4.5.0
psutil>=5.8.0 psutil>=5.8.0
pynvml>=11.0.0 pynvml>=11.0.0
flash-attn>=2.0.0 flash-attn>=2.0.0
pyinstaller>=5.0.0 pyinstaller>=5.0.0
\ No newline at end of file PyMySQL>=1.0.0
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video AI Analysis - Professional Content Creation Tools</title>
<meta name="description" content="Advanced AI-powered video analysis tools designed for adult content creators. Automated scene detection, content moderation, and professional video processing.">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* Header */
header {
padding: 2rem 0;
text-align: center;
color: white;
}
.logo {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 1rem;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.tagline {
font-size: 1.2rem;
opacity: 0.9;
margin-bottom: 2rem;
}
/* Hero Section */
.hero {
text-align: center;
padding: 4rem 0;
color: white;
}
.hero h1 {
font-size: 3.5rem;
font-weight: 700;
margin-bottom: 1rem;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.hero .highlight {
color: #ffd700;
font-weight: 700;
}
.hero p {
font-size: 1.3rem;
margin-bottom: 2rem;
opacity: 0.9;
}
.cta-buttons {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 3rem;
}
.btn {
padding: 1rem 2rem;
border: none;
border-radius: 50px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
text-decoration: none;
display: inline-block;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
}
.btn-primary {
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
color: white;
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(255, 107, 107, 0.6);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.1);
color: white;
border: 2px solid white;
backdrop-filter: blur(10px);
}
.btn-secondary:hover {
background: white;
color: #667eea;
}
/* Features Section */
.features {
padding: 4rem 0;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
}
.features h2 {
text-align: center;
font-size: 2.5rem;
margin-bottom: 3rem;
color: #333;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-bottom: 3rem;
}
.feature-card {
background: white;
padding: 2rem;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
text-align: center;
transition: transform 0.3s ease;
}
.feature-card:hover {
transform: translateY(-5px);
}
.feature-icon {
font-size: 3rem;
color: #667eea;
margin-bottom: 1rem;
}
.feature-card h3 {
font-size: 1.5rem;
margin-bottom: 1rem;
color: #333;
}
.feature-card p {
color: #666;
line-height: 1.6;
}
/* Adult Content Section */
.adult-content {
padding: 4rem 0;
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
color: white;
text-align: center;
}
.adult-content h2 {
font-size: 2.5rem;
margin-bottom: 2rem;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.adult-content p {
font-size: 1.2rem;
margin-bottom: 2rem;
opacity: 0.9;
}
.adult-features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
max-width: 1000px;
margin: 0 auto;
}
.adult-feature {
background: rgba(255, 255, 255, 0.1);
padding: 2rem;
border-radius: 15px;
backdrop-filter: blur(10px);
}
.adult-feature h3 {
font-size: 1.3rem;
margin-bottom: 1rem;
}
/* Pricing Section */
.pricing {
padding: 4rem 0;
background: white;
text-align: center;
}
.pricing h2 {
font-size: 2.5rem;
margin-bottom: 1rem;
color: #333;
}
.pricing .subtitle {
font-size: 1.2rem;
color: #666;
margin-bottom: 3rem;
}
.pricing-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
max-width: 1000px;
margin: 0 auto;
}
.pricing-card {
border: 2px solid #e0e0e0;
border-radius: 15px;
padding: 2rem;
transition: all 0.3s ease;
}
.pricing-card:hover {
border-color: #667eea;
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.2);
}
.pricing-card.popular {
border-color: #667eea;
position: relative;
}
.pricing-card.popular::before {
content: 'Most Popular';
position: absolute;
top: -10px;
left: 50%;
transform: translateX(-50%);
background: #667eea;
color: white;
padding: 5px 15px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
}
.pricing-card h3 {
font-size: 1.8rem;
margin-bottom: 1rem;
color: #333;
}
.price {
font-size: 2.5rem;
font-weight: 700;
color: #667eea;
margin-bottom: 1rem;
}
.price span {
font-size: 1rem;
font-weight: 400;
color: #666;
}
.pricing-features {
list-style: none;
text-align: left;
margin: 2rem 0;
}
.pricing-features li {
padding: 0.5rem 0;
border-bottom: 1px solid #f0f0f0;
}
.pricing-features li:before {
content: '✓';
color: #28a745;
font-weight: bold;
margin-right: 10px;
}
/* Footer */
footer {
padding: 3rem 0;
background: #333;
color: white;
text-align: center;
}
.footer-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.footer-section h3 {
margin-bottom: 1rem;
color: #fff;
}
.footer-section p, .footer-section a {
color: #ccc;
text-decoration: none;
line-height: 1.6;
}
.footer-section a:hover {
color: #667eea;
}
.social-links {
display: flex;
justify-content: center;
gap: 1rem;
margin: 2rem 0;
}
.social-links a {
color: white;
font-size: 1.5rem;
transition: color 0.3s ease;
}
.social-links a:hover {
color: #667eea;
}
.copyright {
border-top: 1px solid #444;
padding-top: 2rem;
color: #888;
}
/* Responsive Design */
@media (max-width: 768px) {
.hero h1 {
font-size: 2.5rem;
}
.cta-buttons {
flex-direction: column;
align-items: center;
}
.features-grid, .adult-features, .pricing-grid {
grid-template-columns: 1fr;
}
.footer-content {
grid-template-columns: 1fr;
}
}
/* Animations */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in-up {
animation: fadeInUp 0.6s ease-out;
}
</style>
</head>
<body>
<header>
<div class="container">
<div class="logo">Video AI Pro</div>
<div class="tagline">Professional AI-Powered Video Analysis for Content Creators</div>
</div>
</header>
<section class="hero">
<div class="container">
<h1>Revolutionize Your <span class="highlight">Content Creation</span></h1>
<p>Advanced AI video analysis tools designed specifically for adult content creators. Automate scene detection, content moderation, and professional video processing with enterprise-grade accuracy.</p>
<div class="cta-buttons">
<a href="/register" class="btn btn-primary">Start Free Trial</a>
<a href="/login" class="btn btn-secondary">Login</a>
</div>
<p style="font-size: 1rem; opacity: 0.8;">✨ No setup required • ⚡ Instant processing • 🔒 Secure & private</p>
</div>
</section>
<section class="features">
<div class="container">
<h2>Powerful AI Features</h2>
<div class="features-grid">
<div class="feature-card fade-in-up">
<div class="feature-icon"><i class="fas fa-brain"></i></div>
<h3>Advanced Scene Detection</h3>
<p>Automatically identify and categorize scenes with AI-powered analysis. Perfect for content organization and quality control.</p>
</div>
<div class="feature-card fade-in-up">
<div class="feature-icon"><i class="fas fa-shield-alt"></i></div>
<h3>Content Moderation</h3>
<p>Intelligent content filtering and moderation tools to ensure compliance and maintain professional standards.</p>
</div>
<div class="feature-card fade-in-up">
<div class="feature-icon"><i class="fas fa-chart-line"></i></div>
<h3>Quality Analytics</h3>
<p>Detailed analytics and insights into your content performance, engagement metrics, and audience preferences.</p>
</div>
<div class="feature-card fade-in-up">
<div class="feature-icon"><i class="fas fa-cloud"></i></div>
<h3>Cloud Processing</h3>
<p>Scale your processing power with cloud GPUs. No hardware limitations, pay only for what you use.</p>
</div>
<div class="feature-card fade-in-up">
<div class="feature-icon"><i class="fas fa-clock"></i></div>
<h3>Real-time Processing</h3>
<p>Process videos in real-time with our optimized AI models. Get results instantly, not hours later.</p>
</div>
<div class="feature-card fade-in-up">
<div class="feature-icon"><i class="fas fa-lock"></i></div>
<h3>Privacy First</h3>
<p>Your content stays private. All processing happens in secure, isolated environments with end-to-end encryption.</p>
</div>
</div>
</div>
</section>
<section class="adult-content">
<div class="container">
<h2>Built for Adult Content Creators</h2>
<p>Specialized tools designed with the unique needs of adult entertainment professionals in mind.</p>
<div class="adult-features">
<div class="adult-feature">
<h3>🎬 Scene Recognition</h3>
<p>Automatically detect scene changes, transitions, and content boundaries with industry-leading accuracy.</p>
</div>
<div class="adult-feature">
<h3>🏷️ Content Categorization</h3>
<p>Smart tagging and categorization of content types, performers, and themes for better organization.</p>
</div>
<div class="adult-feature">
<h3>📊 Performance Analytics</h3>
<p>Detailed insights into content performance, viewer engagement, and market trends.</p>
</div>
<div class="adult-feature">
<h3>🔍 Quality Assurance</h3>
<p>Automated quality checks, resolution verification, and content compliance monitoring.</p>
</div>
</div>
</div>
</section>
<section class="pricing">
<div class="container">
<h2>Simple, Transparent Pricing</h2>
<div class="subtitle">Pay only for what you process. No hidden fees, no subscriptions.</div>
<div class="pricing-grid">
<div class="pricing-card">
<h3>Starter</h3>
<div class="price">Free</div>
<ul class="pricing-features">
<li>100 analysis tokens</li>
<li>Basic scene detection</li>
<li>Standard quality processing</li>
<li>Email support</li>
</ul>
<a href="/register" class="btn btn-primary">Get Started</a>
</div>
<div class="pricing-card popular">
<h3>Professional</h3>
<div class="price">$0.10<span>/token</span></div>
<ul class="pricing-features">
<li>Advanced AI analysis</li>
<li>High-quality processing</li>
<li>Content categorization</li>
<li>Performance analytics</li>
<li>Priority support</li>
</ul>
<a href="/register" class="btn btn-primary">Start Professional</a>
</div>
<div class="pricing-card">
<h3>Enterprise</h3>
<div class="price">$0.05<span>/token</span></div>
<ul class="pricing-features">
<li>Everything in Professional</li>
<li>Custom AI models</li>
<li>Dedicated cloud GPUs</li>
<li>API access</li>
<li>24/7 phone support</li>
</ul>
<a href="/contact" class="btn btn-secondary">Contact Sales</a>
</div>
</div>
</div>
</section>
<footer>
<div class="container">
<div class="footer-content">
<div class="footer-section">
<h3>Video AI Pro</h3>
<p>Professional AI-powered video analysis tools for content creators worldwide. Built with privacy, security, and performance in mind.</p>
</div>
<div class="footer-section">
<h3>Resources</h3>
<p><a href="/docs">Documentation</a></p>
<p><a href="/api">API Reference</a></p>
<p><a href="/support">Support Center</a></p>
<p><a href="/privacy">Privacy Policy</a></p>
</div>
<div class="footer-section">
<h3>Community</h3>
<p><a href="https://github.com/sexhack/vidai" target="_blank">GitHub Repository</a></p>
<p><a href="https://www.sexhack.me" target="_blank">SexHack.me</a></p>
<p><a href="/blog">Developer Blog</a></p>
<p><a href="/contact">Contact Us</a></p>
</div>
</div>
<div class="social-links">
<a href="#" title="GitHub"><i class="fab fa-github"></i></a>
<a href="#" title="Twitter"><i class="fab fa-twitter"></i></a>
<a href="#" title="Discord"><i class="fab fa-discord"></i></a>
<a href="#" title="LinkedIn"><i class="fab fa-linkedin"></i></a>
</div>
<div class="copyright">
<p>&copy; 2024 Video AI Pro. All rights reserved. | Created by <a href="https://www.sexhack.me" style="color: #667eea;">SexHack.me</a></p>
<p>Licensed under <a href="/license" style="color: #667eea;">GPLv3</a> | <a href="https://github.com/sexhack/vidai" style="color: #667eea;">View Source</a></p>
</div>
</div>
</footer>
<script>
// Add fade-in animation to elements
document.addEventListener('DOMContentLoaded', function() {
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
};
const observer = new IntersectionObserver(function(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('fade-in-up');
}
});
}, observerOptions);
// Observe all feature cards
document.querySelectorAll('.feature-card').forEach(card => {
observer.observe(card);
});
});
</script>
</body>
</html>
\ No newline at end of file
...@@ -109,4 +109,16 @@ def api_authenticate(token: str) -> Optional[Dict[str, Any]]: ...@@ -109,4 +109,16 @@ def api_authenticate(token: str) -> Optional[Dict[str, Any]]:
def generate_api_token(user_id: int) -> str: def generate_api_token(user_id: int) -> str:
"""Generate API token for user.""" """Generate API token for user."""
from .database import create_api_token from .database import create_api_token
return create_api_token(user_id) return create_api_token(user_id)
\ No newline at end of file
def register_user(username: str, password: str, email: str) -> tuple[bool, str]:
"""Register a new user with email confirmation."""
from .database import register_user as db_register_user
return db_register_user(username, password, email)
def confirm_email(token: str) -> bool:
"""Confirm user email with token."""
from .database import confirm_email as db_confirm_email
return db_confirm_email(token)
\ No newline at end of file
...@@ -249,5 +249,127 @@ def get_all_settings() -> dict: ...@@ -249,5 +249,127 @@ def get_all_settings() -> dict:
'cluster_port': int(config.get('cluster_port', '5003')), 'cluster_port': int(config.get('cluster_port', '5003')),
'cluster_token': config.get('cluster_token', ''), 'cluster_token': config.get('cluster_token', ''),
'cluster_client': config.get('cluster_client', 'false').lower() == 'true', 'cluster_client': config.get('cluster_client', 'false').lower() == 'true',
'system_prompt': get_system_prompt_content() 'system_prompt': get_system_prompt_content(),
} # New settings for registration and payments
\ No newline at end of file 'allow_registration': config.get('allow_registration', 'true').lower() == 'true',
'default_user_tokens': int(config.get('default_user_tokens', '100')),
'token_price_usd': float(config.get('token_price_usd', '0.10')),
'smtp_server': config.get('smtp_server', 'smtp.gmail.com'),
'smtp_port': int(config.get('smtp_port', '587')),
'smtp_username': config.get('smtp_username', ''),
'smtp_use_tls': config.get('smtp_use_tls', 'true').lower() == 'true',
'smtp_use_ssl': config.get('smtp_use_ssl', 'false').lower() == 'true',
'stripe_publishable_key': config.get('stripe_publishable_key', ''),
'paypal_client_id': config.get('paypal_client_id', ''),
'btc_payment_address': config.get('btc_payment_address', ''),
'eth_payment_address': config.get('eth_payment_address', ''),
'base_url': config.get('base_url', 'http://localhost:5000'),
# Database settings
'db_type': config.get('db_type', 'sqlite'),
'db_sqlite_path': config.get('db_sqlite_path', 'vidai.db'),
'db_mysql_host': config.get('db_mysql_host', 'localhost'),
'db_mysql_port': int(config.get('db_mysql_port', '3306')),
'db_mysql_user': config.get('db_mysql_user', 'vidai'),
'db_mysql_password': config.get('db_mysql_password', ''),
'db_mysql_database': config.get('db_mysql_database', 'vidai'),
'db_mysql_charset': config.get('db_mysql_charset', 'utf8mb4')
}
# Registration and user management settings
def set_allow_registration(allow: bool) -> None:
"""Enable/disable user registration."""
set_config('allow_registration', 'true' if allow else 'false')
def get_allow_registration() -> bool:
"""Check if user registration is allowed."""
return get_config('allow_registration', 'true').lower() == 'true'
def set_default_user_tokens(tokens: int) -> None:
"""Set default token allocation for new users."""
set_config('default_user_tokens', str(tokens))
def get_default_user_tokens() -> int:
"""Get default token allocation for new users."""
return int(get_config('default_user_tokens', '100'))
def set_token_price(price: float) -> None:
"""Set price per token in USD."""
set_config('token_price_usd', str(price))
def get_token_price() -> float:
"""Get price per token in USD."""
return float(get_config('token_price_usd', '0.10'))
# Email settings
def set_smtp_server(server: str) -> None:
"""Set SMTP server."""
set_config('smtp_server', server)
def set_smtp_port(port: int) -> None:
"""Set SMTP port."""
set_config('smtp_port', str(port))
def set_smtp_credentials(username: str, password: str) -> None:
"""Set SMTP credentials."""
set_config('smtp_username', username)
set_config('smtp_password', password)
def set_smtp_security(use_tls: bool, use_ssl: bool) -> None:
"""Set SMTP security options."""
set_config('smtp_use_tls', 'true' if use_tls else 'false')
set_config('smtp_use_ssl', 'true' if use_ssl else 'false')
# Payment settings
def set_stripe_keys(publishable_key: str, secret_key: str) -> None:
"""Set Stripe API keys."""
set_config('stripe_publishable_key', publishable_key)
set_config('stripe_secret_key', secret_key)
def set_paypal_credentials(client_id: str, client_secret: str) -> None:
"""Set PayPal credentials."""
set_config('paypal_client_id', client_id)
set_config('paypal_client_secret', client_secret)
def set_crypto_addresses(btc_address: str, eth_address: str) -> None:
"""Set cryptocurrency payment addresses."""
set_config('btc_payment_address', btc_address)
set_config('eth_payment_address', eth_address)
def set_base_url(url: str) -> None:
"""Set base URL for the application."""
set_config('base_url', url)
# Database settings
def set_db_type(db_type: str) -> None:
"""Set database type (sqlite or mysql)."""
set_config('db_type', db_type)
def set_db_sqlite_path(path: str) -> None:
"""Set SQLite database path."""
set_config('db_sqlite_path', path)
def set_db_mysql_config(host: str, port: int, user: str, password: str, database: str, charset: str = 'utf8mb4') -> None:
"""Set MySQL database configuration."""
set_config('db_mysql_host', host)
set_config('db_mysql_port', str(port))
set_config('db_mysql_user', user)
set_config('db_mysql_password', password)
set_config('db_mysql_database', database)
set_config('db_mysql_charset', charset)
\ No newline at end of file
...@@ -19,94 +19,215 @@ Database management for Video AI. ...@@ -19,94 +19,215 @@ Database management for Video AI.
Uses SQLite for persistent configuration storage. Uses SQLite for persistent configuration storage.
""" """
import sqlite3
import os import os
import json import json
from typing import Dict, Any, Optional, List from typing import Dict, Any, Optional, List
from .compat import get_user_config_dir, ensure_dir from .compat import get_user_config_dir, ensure_dir
# Database imports - conditionally import MySQL
try:
import pymysql
MYSQL_AVAILABLE = True
except ImportError:
MYSQL_AVAILABLE = False
try:
import sqlite3
SQLITE_AVAILABLE = True
except ImportError:
SQLITE_AVAILABLE = False
# Database configuration
def get_db_config() -> Dict[str, Any]:
"""Get database configuration."""
from .config import get_config
return {
'type': get_config('db_type', 'sqlite'),
'sqlite_path': os.path.join(get_user_config_dir(), get_config('db_sqlite_path', 'vidai.db')),
'mysql_host': get_config('db_mysql_host', 'localhost'),
'mysql_port': int(get_config('db_mysql_port', '3306')),
'mysql_user': get_config('db_mysql_user', 'vidai'),
'mysql_password': get_config('db_mysql_password', ''),
'mysql_database': get_config('db_mysql_database', 'vidai'),
'mysql_charset': get_config('db_mysql_charset', 'utf8mb4')
}
def get_db_connection():
"""Get database connection based on configuration."""
config = get_db_config()
DB_PATH = os.path.join(get_user_config_dir(), 'vidai.db') if config['type'] == 'mysql':
if not MYSQL_AVAILABLE:
raise ImportError("MySQL support not available. Install with: pip install PyMySQL")
conn = pymysql.connect(
host=config['mysql_host'],
port=config['mysql_port'],
user=config['mysql_user'],
password=config['mysql_password'],
database=config['mysql_database'],
charset=config['mysql_charset'],
cursorclass=pymysql.cursors.DictCursor,
autocommit=False
)
else: # sqlite
if not SQLITE_AVAILABLE:
raise ImportError("SQLite support not available")
ensure_dir(os.path.dirname(config['sqlite_path']))
conn = sqlite3.connect(config['sqlite_path'])
conn.row_factory = sqlite3.Row
def get_db_connection() -> sqlite3.Connection:
"""Get database connection, creating database if it doesn't exist."""
ensure_dir(os.path.dirname(DB_PATH))
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
init_db(conn) init_db(conn)
return conn return conn
def init_db(conn: sqlite3.Connection) -> None: def init_db(conn) -> None:
"""Initialize database tables if they don't exist.""" """Initialize database tables if they don't exist."""
cursor = conn.cursor() cursor = conn.cursor()
config = get_db_config()
# Configuration table # Configuration table
cursor.execute(''' if config['type'] == 'mysql':
CREATE TABLE IF NOT EXISTS config ( cursor.execute('''
key TEXT PRIMARY KEY, CREATE TABLE IF NOT EXISTS config (
value TEXT NOT NULL `key` VARCHAR(255) PRIMARY KEY,
) value TEXT NOT NULL
''') ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
''')
else:
cursor.execute('''
CREATE TABLE IF NOT EXISTS config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
)
''')
# System prompts table # System prompts table
cursor.execute(''' if config['type'] == 'mysql':
CREATE TABLE IF NOT EXISTS system_prompts ( cursor.execute('''
id INTEGER PRIMARY KEY, CREATE TABLE IF NOT EXISTS system_prompts (
name TEXT UNIQUE NOT NULL, id INT AUTO_INCREMENT PRIMARY KEY,
content TEXT NOT NULL, name VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, content TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
) updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
''') ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
''')
else:
cursor.execute('''
CREATE TABLE IF NOT EXISTS system_prompts (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Users table # Users table
cursor.execute(''' if config['type'] == 'mysql':
CREATE TABLE IF NOT EXISTS users ( cursor.execute('''
id INTEGER PRIMARY KEY, CREATE TABLE IF NOT EXISTS users (
username TEXT UNIQUE NOT NULL, id INT AUTO_INCREMENT PRIMARY KEY,
password_hash TEXT NOT NULL, username VARCHAR(255) UNIQUE NOT NULL,
email TEXT UNIQUE, password_hash VARCHAR(255) NOT NULL,
role TEXT NOT NULL DEFAULT 'user', email VARCHAR(255) UNIQUE,
active BOOLEAN DEFAULT 1, role VARCHAR(50) NOT NULL DEFAULT 'user',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, active BOOLEAN DEFAULT 0,
last_login TIMESTAMP email_confirmed BOOLEAN DEFAULT 0,
) email_confirmation_token VARCHAR(255),
''') email_confirmation_expires TIMESTAMP NULL,
tokens INT DEFAULT 100,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
''')
else:
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
email TEXT UNIQUE,
role TEXT NOT NULL DEFAULT 'user',
active BOOLEAN DEFAULT 0,
email_confirmed BOOLEAN DEFAULT 0,
email_confirmation_token TEXT,
email_confirmation_expires TIMESTAMP,
tokens INTEGER DEFAULT 100,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP
)
''')
# API tokens table # API tokens table
cursor.execute(''' if config['type'] == 'mysql':
CREATE TABLE IF NOT EXISTS api_tokens ( cursor.execute('''
id INTEGER PRIMARY KEY, CREATE TABLE IF NOT EXISTS api_tokens (
user_id INTEGER NOT NULL, id INT AUTO_INCREMENT PRIMARY KEY,
token TEXT UNIQUE NOT NULL, user_id INT NOT NULL,
expires_at TIMESTAMP, token VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NULL,
FOREIGN KEY (user_id) REFERENCES users (id) created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
) FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
''') ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
''')
else:
cursor.execute('''
CREATE TABLE IF NOT EXISTS api_tokens (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
token TEXT UNIQUE NOT NULL,
expires_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id)
)
''')
# Processing queue table # Processing queue table
cursor.execute(''' if config['type'] == 'mysql':
CREATE TABLE IF NOT EXISTS processing_queue ( cursor.execute('''
id INTEGER PRIMARY KEY, CREATE TABLE IF NOT EXISTS processing_queue (
user_id INTEGER NOT NULL, id INT AUTO_INCREMENT PRIMARY KEY,
request_type TEXT NOT NULL, user_id INT NOT NULL,
status TEXT NOT NULL DEFAULT 'queued', request_type VARCHAR(50) NOT NULL,
priority INTEGER DEFAULT 0, status VARCHAR(20) NOT NULL DEFAULT 'queued',
data TEXT NOT NULL, priority INT DEFAULT 0,
result TEXT, data TEXT NOT NULL,
error_message TEXT, result TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, error_message TEXT,
started_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP, started_at TIMESTAMP NULL,
estimated_time INTEGER, completed_at TIMESTAMP NULL,
estimated_tokens INTEGER DEFAULT 0, estimated_time INT,
used_tokens INTEGER DEFAULT 0, estimated_tokens INT DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users (id) used_tokens INT DEFAULT 0,
) FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
''') ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
''')
else:
cursor.execute('''
CREATE TABLE IF NOT EXISTS processing_queue (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
request_type TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'queued',
priority INTEGER DEFAULT 0,
data TEXT NOT NULL,
result TEXT,
error_message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
started_at TIMESTAMP,
completed_at TIMESTAMP,
estimated_time INTEGER,
estimated_tokens INTEGER DEFAULT 0,
used_tokens INTEGER DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users (id)
)
''')
# Insert default configurations if not exist # Insert default configurations if not exist
defaults = { defaults = {
...@@ -130,17 +251,28 @@ def init_db(conn: sqlite3.Connection) -> None: ...@@ -130,17 +251,28 @@ def init_db(conn: sqlite3.Connection) -> None:
} }
for key, value in defaults.items(): for key, value in defaults.items():
cursor.execute('INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)', (key, value)) if config['type'] == 'mysql':
cursor.execute('INSERT IGNORE INTO config (`key`, value) VALUES (?, ?)', (key, value))
else:
cursor.execute('INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)', (key, value))
# Insert default system prompt if not exist # Insert default system prompt if not exist
cursor.execute('INSERT OR IGNORE INTO system_prompts (name, content) VALUES (?, ?)', if config['type'] == 'mysql':
('default', 'when the action done by the person or persons in the frame changes, or where the scenario change, or where there an active action after a long time of no actions happening')) cursor.execute('INSERT IGNORE INTO system_prompts (name, content) VALUES (?, ?)',
('default', 'when the action done by the person or persons in the frame changes, or where the scenario change, or where there an active action after a long time of no actions happening'))
else:
cursor.execute('INSERT OR IGNORE INTO system_prompts (name, content) VALUES (?, ?)',
('default', 'when the action done by the person or persons in the frame changes, or where the scenario change, or where there an active action after a long time of no actions happening'))
# Insert default admin user if not exist # Insert default admin user if not exist
import hashlib import hashlib
default_password = hashlib.sha256('admin'.encode()).hexdigest() default_password = hashlib.sha256('admin'.encode()).hexdigest()
cursor.execute('INSERT OR IGNORE INTO users (username, password_hash, role) VALUES (?, ?, ?)', if config['type'] == 'mysql':
('admin', default_password, 'admin')) cursor.execute('INSERT IGNORE INTO users (username, password_hash, role, active, email_confirmed) VALUES (?, ?, ?, 1, 1)',
('admin', default_password, 'admin'))
else:
cursor.execute('INSERT OR IGNORE INTO users (username, password_hash, role, active, email_confirmed) VALUES (?, ?, ?, 1, 1)',
('admin', default_password, 'admin'))
conn.commit() conn.commit()
...@@ -159,7 +291,13 @@ def set_config(key: str, value: str) -> None: ...@@ -159,7 +291,13 @@ def set_config(key: str, value: str) -> None:
"""Set configuration value.""" """Set configuration value."""
conn = get_db_connection() conn = get_db_connection()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)', (key, value)) config = get_db_config()
if config['type'] == 'mysql':
cursor.execute('INSERT INTO config (`key`, value) VALUES (?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value)', (key, value))
else:
cursor.execute('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)', (key, value))
conn.commit() conn.commit()
conn.close() conn.close()
...@@ -188,10 +326,20 @@ def set_system_prompt(name: str, content: str) -> None: ...@@ -188,10 +326,20 @@ def set_system_prompt(name: str, content: str) -> None:
"""Set system prompt.""" """Set system prompt."""
conn = get_db_connection() conn = get_db_connection()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(''' config = get_db_config()
INSERT OR REPLACE INTO system_prompts (name, content, updated_at)
VALUES (?, ?, CURRENT_TIMESTAMP) if config['type'] == 'mysql':
''', (name, content)) cursor.execute('''
INSERT INTO system_prompts (name, content, updated_at)
VALUES (?, ?, CURRENT_TIMESTAMP)
ON DUPLICATE KEY UPDATE content = VALUES(content), updated_at = CURRENT_TIMESTAMP
''', (name, content))
else:
cursor.execute('''
INSERT OR REPLACE INTO system_prompts (name, content, updated_at)
VALUES (?, ?, CURRENT_TIMESTAMP)
''', (name, content))
conn.commit() conn.commit()
conn.close() conn.close()
...@@ -224,13 +372,89 @@ def create_user(username: str, password: str, email: str = None, role: str = 'us ...@@ -224,13 +372,89 @@ def create_user(username: str, password: str, email: str = None, role: str = 'us
conn.close() conn.close()
def register_user(username: str, password: str, email: str) -> tuple[bool, str]:
"""Register a new user with email confirmation."""
import hashlib
import secrets
import time
password_hash = hashlib.sha256(password.encode()).hexdigest()
confirmation_token = secrets.token_hex(32)
expires_at = int(time.time()) + (24 * 60 * 60) # 24 hours
conn = get_db_connection()
cursor = conn.cursor()
try:
cursor.execute('''
INSERT INTO users (username, password_hash, email, email_confirmation_token, email_confirmation_expires)
VALUES (?, ?, ?, ?, ?)
''', (username, password_hash, email, confirmation_token, expires_at))
conn.commit()
return True, confirmation_token
except sqlite3.IntegrityError as e:
if 'username' in str(e):
return False, "Username already exists"
elif 'email' in str(e):
return False, "Email already registered"
return False, "Registration failed"
finally:
conn.close()
def confirm_email(token: str) -> bool:
"""Confirm user email with token."""
import time
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('''
UPDATE users
SET active = 1, email_confirmed = 1, email_confirmation_token = NULL, email_confirmation_expires = NULL
WHERE email_confirmation_token = ? AND email_confirmation_expires > ?
''', (token, int(time.time())))
conn.commit()
success = cursor.rowcount > 0
conn.close()
return success
def get_user_tokens(user_id: int) -> int:
"""Get user's current token balance."""
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('SELECT tokens FROM users WHERE id = ?', (user_id,))
row = cursor.fetchone()
conn.close()
return row['tokens'] if row else 0
def update_user_tokens(user_id: int, token_change: int) -> bool:
"""Update user's token balance."""
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('UPDATE users SET tokens = tokens + ? WHERE id = ?', (token_change, user_id))
conn.commit()
success = cursor.rowcount > 0
conn.close()
return success
def get_default_user_tokens() -> int:
"""Get default token allocation for new users."""
return int(get_config('default_user_tokens', '100'))
def get_token_price() -> float:
"""Get price per token in USD."""
return float(get_config('token_price_usd', '0.10'))
def authenticate_user(username: str, password: str) -> Optional[Dict[str, Any]]: def authenticate_user(username: str, password: str) -> Optional[Dict[str, Any]]:
"""Authenticate user and return user info.""" """Authenticate user and return user info."""
import hashlib import hashlib
password_hash = hashlib.sha256(password.encode()).hexdigest() password_hash = hashlib.sha256(password.encode()).hexdigest()
conn = get_db_connection() conn = get_db_connection()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute('SELECT id, username, email, role, active FROM users WHERE username = ? AND password_hash = ?', cursor.execute('SELECT id, username, email, role, active, email_confirmed, tokens FROM users WHERE username = ? AND password_hash = ? AND active = 1 AND email_confirmed = 1',
(username, password_hash)) (username, password_hash))
row = cursor.fetchone() row = cursor.fetchone()
if row: if row:
......
# Video AI Email Module
# Copyright (C) 2024 Stefy Lanza <stefy@sexhack.me>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
Email sending functionality for Video AI.
Supports SMTP and simple email sending.
"""
import smtplib
import ssl
import time
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import Optional
from .config import get_config
def get_smtp_config() -> dict:
"""Get SMTP configuration."""
return {
'server': get_config('smtp_server', 'smtp.gmail.com'),
'port': int(get_config('smtp_port', '587')),
'username': get_config('smtp_username', ''),
'password': get_config('smtp_password', ''),
'use_tls': get_config('smtp_use_tls', 'true').lower() == 'true',
'use_ssl': get_config('smtp_use_ssl', 'false').lower() == 'true'
}
def send_email(to_email: str, subject: str, html_content: str, text_content: str = None) -> bool:
"""Send an email."""
config = get_smtp_config()
if not config['username'] or not config['password']:
print("SMTP not configured. Email sending disabled.")
return False
try:
# Create message
message = MIMEMultipart("alternative")
message["Subject"] = subject
message["From"] = config['username']
message["To"] = to_email
# Add text content
if text_content:
message.attach(MIMEText(text_content, "plain"))
# Add HTML content
message.attach(MIMEText(html_content, "html"))
# Create SMTP connection
if config['use_ssl']:
server = smtplib.SMTP_SSL(config['server'], config['port'])
else:
server = smtplib.SMTP(config['server'], config['port'])
if config['use_tls']:
server.starttls()
# Login and send
server.login(config['username'], config['password'])
server.sendmail(config['username'], to_email, message.as_string())
server.quit()
return True
except Exception as e:
print(f"Email sending failed: {e}")
return False
def send_registration_confirmation(email: str, username: str, confirmation_token: str) -> bool:
"""Send email confirmation for user registration."""
base_url = get_config('base_url', 'http://localhost:5000')
confirmation_url = f"{base_url}/confirm/{confirmation_token}"
subject = "Welcome to Video AI - Please Confirm Your Email"
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Email Confirmation</title>
<style>
body {{ font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 20px; }}
.container {{ max-width: 600px; margin: auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }}
h1 {{ color: #333; text-align: center; }}
.btn {{ display: inline-block; padding: 12px 24px; background: #007bff; color: white; text-decoration: none; border-radius: 5px; margin: 20px 0; }}
.footer {{ margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; color: #666; font-size: 12px; }}
</style>
</head>
<body>
<div class="container">
<h1>Welcome to Video AI!</h1>
<p>Hello {username},</p>
<p>Thank you for registering with Video AI. To complete your registration and start using our professional video analysis tools, please confirm your email address by clicking the button below:</p>
<div style="text-align: center;">
<a href="{confirmation_url}" class="btn">Confirm Email Address</a>
</div>
<p>If the button doesn't work, you can also copy and paste this link into your browser:</p>
<p><a href="{confirmation_url}">{confirmation_url}</a></p>
<p>This link will expire in 24 hours for security reasons.</p>
<div class="footer">
<p>If you didn't create an account with Video AI, please ignore this email.</p>
<p>&copy; 2024 Video AI. All rights reserved.</p>
</div>
</div>
</body>
</html>
"""
text_content = f"""
Welcome to Video AI!
Hello {username},
Thank you for registering with Video AI. To complete your registration, please confirm your email address by visiting:
{confirmation_url}
This link will expire in 24 hours.
If you didn't create an account, please ignore this email.
Best regards,
Video AI Team
"""
return send_email(email, subject, html_content, text_content)
def send_password_reset(email: str, username: str, reset_token: str) -> bool:
"""Send password reset email."""
base_url = get_config('base_url', 'http://localhost:5000')
reset_url = f"{base_url}/reset-password/{reset_token}"
subject = "Video AI - Password Reset Request"
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Password Reset</title>
<style>
body {{ font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 20px; }}
.container {{ max-width: 600px; margin: auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }}
h1 {{ color: #333; text-align: center; }}
.btn {{ display: inline-block; padding: 12px 24px; background: #dc3545; color: white; text-decoration: none; border-radius: 5px; margin: 20px 0; }}
.footer {{ margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; color: #666; font-size: 12px; }}
</style>
</head>
<body>
<div class="container">
<h1>Password Reset Request</h1>
<p>Hello {username},</p>
<p>We received a request to reset your password for your Video AI account. Click the button below to reset your password:</p>
<div style="text-align: center;">
<a href="{reset_url}" class="btn">Reset Password</a>
</div>
<p>If you didn't request a password reset, please ignore this email. Your password will remain unchanged.</p>
<p>This link will expire in 1 hour for security reasons.</p>
<div class="footer">
<p>&copy; 2024 Video AI. All rights reserved.</p>
</div>
</div>
</body>
</html>
"""
text_content = f"""
Password Reset Request
Hello {username},
We received a request to reset your password. Visit this link to reset it:
{reset_url}
This link expires in 1 hour. If you didn't request this, ignore this email.
Best regards,
Video AI Team
"""
return send_email(email, subject, html_content, text_content)
def send_payment_confirmation(email: str, username: str, tokens_purchased: int, amount_paid: float, payment_method: str) -> bool:
"""Send payment confirmation email."""
subject = f"Video AI - Payment Confirmation ({tokens_purchased} tokens)"
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Payment Confirmation</title>
<style>
body {{ font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 20px; }}
.container {{ max-width: 600px; margin: auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }}
h1 {{ color: #333; text-align: center; }}
.success {{ background: #d4edda; color: #155724; padding: 15px; border-radius: 5px; margin: 20px 0; }}
.details {{ background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0; }}
.footer {{ margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; color: #666; font-size: 12px; }}
</style>
</head>
<body>
<div class="container">
<h1>Payment Successful!</h1>
<div class="success">
<strong>Thank you for your purchase, {username}!</strong>
</div>
<div class="details">
<h3>Transaction Details:</h3>
<p><strong>Tokens Purchased:</strong> {tokens_purchased}</p>
<p><strong>Amount Paid:</strong> ${amount_paid:.2f}</p>
<p><strong>Payment Method:</strong> {payment_method}</p>
<p><strong>Transaction Date:</strong> {time.strftime('%Y-%m-%d %H:%M:%S')}</p>
</div>
<p>Your tokens have been added to your account and are ready to use for video analysis jobs.</p>
<div class="footer">
<p>If you have any questions about your purchase, please contact our support team.</p>
<p>&copy; 2024 Video AI. All rights reserved.</p>
</div>
</div>
</body>
</html>
"""
text_content = f"""
Payment Confirmation - Video AI
Thank you for your purchase, {username}!
Transaction Details:
- Tokens Purchased: {tokens_purchased}
- Amount Paid: ${amount_paid:.2f}
- Payment Method: {payment_method}
- Date: {time.strftime('%Y-%m-%d %H:%M:%S')}
Your tokens have been added to your account.
Best regards,
Video AI Team
"""
return send_email(email, subject, html_content, text_content)
\ No newline at end of file
# Video AI Payment Processing Module
# Copyright (C) 2024 Stefy Lanza <stefy@sexhack.me>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
Payment processing for Video AI token purchases.
Supports Stripe (credit cards), PayPal, and cryptocurrency payments.
"""
import time
import json
from typing import Dict, Any, Optional, Tuple, List
from .config import get_config
from .database import update_user_tokens
from .email import send_payment_confirmation
class PaymentProcessor:
"""Base class for payment processors."""
def __init__(self):
self.name = "Base"
self.enabled = False
def process_payment(self, user_id: int, tokens: int, amount: float, currency: str = 'USD') -> Tuple[bool, str, Optional[str]]:
"""Process a payment. Returns (success, message, transaction_id)"""
raise NotImplementedError
def create_payment_intent(self, tokens: int, amount: float) -> Optional[Dict[str, Any]]:
"""Create a payment intent for frontend integration."""
return None
class StripeProcessor(PaymentProcessor):
"""Stripe payment processor for credit cards."""
def __init__(self):
super().__init__()
self.name = "Stripe"
self.secret_key = get_config('stripe_secret_key', '')
self.publishable_key = get_config('stripe_publishable_key', '')
self.enabled = bool(self.secret_key and self.publishable_key)
if self.enabled:
try:
import stripe
stripe.api_key = self.secret_key
except ImportError:
self.enabled = False
print("Stripe not available. Install with: pip install stripe")
def process_payment(self, user_id: int, tokens: int, amount: float, currency: str = 'USD') -> Tuple[bool, str, Optional[str]]:
"""Process Stripe payment."""
if not self.enabled:
return False, "Stripe payment processing not configured", None
try:
import stripe
# Create payment intent
intent = stripe.PaymentIntent.create(
amount=int(amount * 100), # Convert to cents
currency=currency.lower(),
metadata={
'user_id': str(user_id),
'tokens': str(tokens)
}
)
return True, "Payment intent created", intent.id
except Exception as e:
return False, f"Payment processing failed: {str(e)}", None
def create_payment_intent(self, tokens: int, amount: float) -> Optional[Dict[str, Any]]:
"""Create Stripe payment intent for frontend."""
if not self.enabled:
return None
try:
import stripe
intent = stripe.PaymentIntent.create(
amount=int(amount * 100),
currency='usd',
metadata={'tokens': str(tokens)}
)
return {
'client_secret': intent.client_secret,
'amount': amount,
'tokens': tokens
}
except Exception as e:
print(f"Stripe payment intent creation failed: {e}")
return None
class PayPalProcessor(PaymentProcessor):
"""PayPal payment processor."""
def __init__(self):
super().__init__()
self.name = "PayPal"
self.client_id = get_config('paypal_client_id', '')
self.client_secret = get_config('paypal_client_secret', '')
self.enabled = bool(self.client_id and self.client_secret)
if self.enabled:
try:
# PayPal SDK would go here
pass
except ImportError:
self.enabled = False
def process_payment(self, user_id: int, tokens: int, amount: float, currency: str = 'USD') -> Tuple[bool, str, Optional[str]]:
"""Process PayPal payment."""
if not self.enabled:
return False, "PayPal payment processing not configured", None
# PayPal integration would go here
# For now, return placeholder
return False, "PayPal integration coming soon", None
class CryptoProcessor(PaymentProcessor):
"""Cryptocurrency payment processor."""
def __init__(self):
super().__init__()
self.name = "Cryptocurrency"
self.btc_address = get_config('btc_payment_address', '')
self.eth_address = get_config('eth_payment_address', '')
self.enabled = bool(self.btc_address or self.eth_address)
def process_payment(self, user_id: int, tokens: int, amount: float, currency: str = 'USD') -> Tuple[bool, str, Optional[str]]:
"""Process cryptocurrency payment."""
if not self.enabled:
return False, "Cryptocurrency payments not configured", None
# Generate payment addresses and instructions
transaction_id = f"crypto_{user_id}_{int(time.time())}"
payment_info = {
'transaction_id': transaction_id,
'amount_usd': amount,
'tokens': tokens,
'btc_address': self.btc_address,
'eth_address': self.eth_address,
'status': 'pending'
}
# In a real implementation, you'd store this in database
# and provide webhook endpoint for payment confirmation
return True, "Cryptocurrency payment initiated. Please send payment to provided address.", transaction_id
# Global payment processors
stripe_processor = StripeProcessor()
paypal_processor = PayPalProcessor()
crypto_processor = CryptoProcessor()
def get_available_processors() -> Dict[str, PaymentProcessor]:
"""Get all available payment processors."""
return {
'stripe': stripe_processor,
'paypal': paypal_processor,
'crypto': crypto_processor
}
def process_payment(user_id: int, processor_name: str, tokens: int, amount: float) -> Tuple[bool, str, Optional[str]]:
"""Process payment using specified processor."""
processors = get_available_processors()
if processor_name not in processors:
return False, "Payment processor not found", None
processor = processors[processor_name]
success, message, transaction_id = processor.process_payment(user_id, tokens, amount)
if success and transaction_id:
# Add tokens to user account
if update_user_tokens(user_id, tokens):
# Send confirmation email
from .database import get_user_by_id
user = get_user_by_id(user_id)
if user and user.get('email'):
send_payment_confirmation(user['email'], user['username'], tokens, amount, processor.name)
return True, f"Payment successful! {tokens} tokens added to your account.", transaction_id
else:
return False, "Payment processed but token update failed", transaction_id
return success, message, transaction_id
def create_payment_intent(processor_name: str, tokens: int, amount: float) -> Optional[Dict[str, Any]]:
"""Create payment intent for frontend integration."""
processors = get_available_processors()
if processor_name not in processors:
return None
processor = processors[processor_name]
return processor.create_payment_intent(tokens, amount)
def get_token_packages() -> List[Dict[str, Any]]:
"""Get available token packages."""
return [
{'tokens': 100, 'price': 10.00, 'popular': False},
{'tokens': 250, 'price': 22.50, 'popular': False},
{'tokens': 500, 'price': 40.00, 'popular': True},
{'tokens': 1000, 'price': 70.00, 'popular': False},
{'tokens': 2500, 'price': 150.00, 'popular': False},
]
def calculate_price(tokens: int) -> float:
"""Calculate price for given number of tokens."""
from .database import get_token_price
return tokens * get_token_price()
\ No newline at end of file
...@@ -32,7 +32,10 @@ from .config import ( ...@@ -32,7 +32,10 @@ from .config import (
set_analysis_backend, set_training_backend, set_default_model, set_frame_interval, set_analysis_backend, set_training_backend, set_default_model, set_frame_interval,
get_comm_type, set_comm_type, set_max_concurrent_jobs get_comm_type, set_comm_type, set_max_concurrent_jobs
) )
from .auth import login_user, logout_user, get_current_user, require_auth, require_admin, api_authenticate from .auth import login_user, logout_user, get_current_user, require_auth, require_admin, api_authenticate, register_user, confirm_email
from .email import send_registration_confirmation
from .database import get_default_user_tokens, update_user_tokens, get_user_tokens, get_token_price
from .payments import get_token_packages, calculate_price, process_payment, create_payment_intent, get_available_processors
from .database import ( from .database import (
create_user, get_all_users, update_user_role, delete_user, create_user, get_all_users, update_user_role, delete_user,
create_api_token, get_user_api_tokens, revoke_api_token create_api_token, get_user_api_tokens, revoke_api_token
...@@ -153,6 +156,125 @@ def login(): ...@@ -153,6 +156,125 @@ def login():
''' '''
return render_template_string(html, error=error) return render_template_string(html, error=error)
@app.route('/register', methods=['GET', 'POST'])
def register():
error = None
success = None
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
confirm_password = request.form.get('confirm_password')
email = request.form.get('email')
if not all([username, password, confirm_password, email]):
error = "All fields are required"
elif password != confirm_password:
error = "Passwords do not match"
elif len(password) < 8:
error = "Password must be at least 8 characters long"
else:
# Register user
success_reg, message = register_user(username, password, email)
if success_reg:
# Send confirmation email
if send_registration_confirmation(email, username, message):
success = "Registration successful! Please check your email to confirm your account."
else:
success = "Registration successful, but email confirmation could not be sent. Please contact support."
else:
error = message
html = '''
<!DOCTYPE html>
<html>
<head>
<title>Register - Video AI</title>
<style>
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
.register-form { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); width: 400px; }
h1 { text-align: center; color: #333; margin-bottom: 30px; }
form { display: flex; flex-direction: column; }
label { margin-bottom: 5px; font-weight: bold; }
input { padding: 10px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; }
button { background: #28a745; color: white; padding: 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
button:hover { background: #218838; }
.error { color: red; margin-bottom: 15px; text-align: center; }
.success { color: green; margin-bottom: 15px; text-align: center; }
.login-link { text-align: center; margin-top: 20px; }
.login-link a { color: #007bff; text-decoration: none; }
</style>
</head>
<body>
<div class="register-form">
<h1>Create Account</h1>
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}
{% if success %}
<div class="success">{{ success }}</div>
{% endif %}
<form method="post">
<label>Username:</label>
<input type="text" name="username" required>
<label>Email:</label>
<input type="email" name="email" required>
<label>Password:</label>
<input type="password" name="password" required>
<label>Confirm Password:</label>
<input type="password" name="confirm_password" required>
<button type="submit">Register</button>
</form>
<div class="login-link">
<a href="/login">Already have an account? Login</a>
</div>
</div>
</body>
</html>
'''
return render_template_string(html, error=error, success=success)
@app.route('/confirm/<token>')
def confirm_email_route(token):
if confirm_email(token):
message = "Email confirmed successfully! You can now login."
success = True
else:
message = "Invalid or expired confirmation link."
success = False
html = f'''
<!DOCTYPE html>
<html>
<head>
<title>Email Confirmation - Video AI</title>
<style>
body {{ font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; display: flex; justify-content: center; align-items: center; min-height: 100vh; }}
.confirm-box {{ background: white; padding: 40px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); width: 400px; text-align: center; }}
h1 {{ color: #333; margin-bottom: 20px; }}
.message {{ margin: 20px 0; padding: 15px; border-radius: 4px; }}
.success {{ background: #d4edda; color: #155724; }}
.error {{ background: #f8d7da; color: #721c24; }}
a {{ display: inline-block; margin-top: 20px; padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; }}
</style>
</head>
<body>
<div class="confirm-box">
<h1>Email Confirmation</h1>
<div class="message {'success' if success else 'error'}">
{message}
</div>
<a href="/login">Continue to Login</a>
</div>
</body>
</html>
'''
return html
@app.route('/logout') @app.route('/logout')
def logout(): def logout():
session_id = get_session_id() session_id = get_session_id()
...@@ -163,6 +285,17 @@ def logout(): ...@@ -163,6 +285,17 @@ def logout():
return resp return resp
@app.route('/') @app.route('/')
def index():
session_id = get_session_id()
user = get_current_user(session_id) if session_id else None
if user:
# Show dashboard for logged-in users
return dashboard()
else:
# Show landing page for non-logged-in users
return render_template_string(open('templates/landing.html').read())
@require_login() @require_login()
def dashboard(): def dashboard():
session_id = get_session_id() session_id = get_session_id()
...@@ -619,6 +752,60 @@ def admin_config(): ...@@ -619,6 +752,60 @@ def admin_config():
if gpu_type: if gpu_type:
set_runpod_gpu_type(gpu_type) set_runpod_gpu_type(gpu_type)
# Registration and token configuration
from .config import set_allow_registration, set_default_user_tokens, set_token_price, set_base_url
set_allow_registration(request.form.get('allow_registration') == 'true')
set_default_user_tokens(int(request.form.get('default_user_tokens', '100')))
set_token_price(float(request.form.get('token_price_usd', '0.10')))
set_base_url(request.form.get('base_url', 'http://localhost:5000'))
# Email configuration
from .config import set_smtp_server, set_smtp_port, set_smtp_credentials, set_smtp_security
set_smtp_server(request.form.get('smtp_server', 'smtp.gmail.com'))
set_smtp_port(int(request.form.get('smtp_port', '587')))
smtp_username = request.form.get('smtp_username')
smtp_password = request.form.get('smtp_password')
if smtp_username and smtp_password:
set_smtp_credentials(smtp_username, smtp_password)
set_smtp_security(
request.form.get('smtp_use_tls') == 'true',
request.form.get('smtp_use_ssl') == 'true'
)
# Payment configuration
from .config import set_stripe_keys, set_paypal_credentials, set_crypto_addresses
stripe_pub = request.form.get('stripe_publishable_key')
stripe_sec = request.form.get('stripe_secret_key')
if stripe_pub and stripe_sec:
set_stripe_keys(stripe_pub, stripe_sec)
paypal_id = request.form.get('paypal_client_id')
paypal_secret = request.form.get('paypal_client_secret')
if paypal_id and paypal_secret:
set_paypal_credentials(paypal_id, paypal_secret)
btc_addr = request.form.get('btc_payment_address')
eth_addr = request.form.get('eth_payment_address')
if btc_addr or eth_addr:
set_crypto_addresses(btc_addr or '', eth_addr or '')
# Database configuration
from .config import set_db_type, set_db_sqlite_path, set_db_mysql_config
db_type = request.form.get('db_type', 'sqlite')
set_db_type(db_type)
if db_type == 'sqlite':
set_db_sqlite_path(request.form.get('db_sqlite_path', 'vidai.db'))
elif db_type == 'mysql':
set_db_mysql_config(
host=request.form.get('db_mysql_host', 'localhost'),
port=int(request.form.get('db_mysql_port', '3306')),
user=request.form.get('db_mysql_user', 'vidai'),
password=request.form.get('db_mysql_password', ''),
database=request.form.get('db_mysql_database', 'vidai'),
charset=request.form.get('db_mysql_charset', 'utf8mb4')
)
settings = get_all_settings() settings = get_all_settings()
html = ''' html = '''
<!DOCTYPE html> <!DOCTYPE html>
...@@ -719,6 +906,130 @@ def admin_config(): ...@@ -719,6 +906,130 @@ def admin_config():
<button type="submit">Save RunPod Configuration</button> <button type="submit">Save RunPod Configuration</button>
</form> </form>
</div> </div>
<div class="section">
<h3>User Registration & Tokens</h3>
<form method="post">
<label>
<input type="checkbox" name="allow_registration" value="true" {% if settings.get('allow_registration', True) %}checked{% endif %}>
<span class="checkbox-label">Allow user registration</span>
</label>
<label>Default Tokens per User:</label>
<input type="number" name="default_user_tokens" value="{{ settings.get('default_user_tokens', 100) }}" min="0">
<label>Token Price (USD per token):</label>
<input type="number" name="token_price_usd" value="{{ settings.get('token_price_usd', 0.10) }}" step="0.01" min="0">
<label>Base URL:</label>
<input type="url" name="base_url" value="{{ settings.get('base_url', 'http://localhost:5000') }}" placeholder="http://your-domain.com">
<button type="submit">Save Registration Settings</button>
</form>
</div>
<div class="section">
<h3>Email Configuration</h3>
<form method="post">
<label>SMTP Server:</label>
<input type="text" name="smtp_server" value="{{ settings.get('smtp_server', 'smtp.gmail.com') }}">
<label>SMTP Port:</label>
<input type="number" name="smtp_port" value="{{ settings.get('smtp_port', 587) }}" min="1" max="65535">
<label>SMTP Username:</label>
<input type="text" name="smtp_username" value="{{ settings.get('smtp_username', '') }}">
<label>SMTP Password:</label>
<input type="password" name="smtp_password" placeholder="Enter SMTP password">
<label>
<input type="checkbox" name="smtp_use_tls" value="true" {% if settings.get('smtp_use_tls', True) %}checked{% endif %}>
<span class="checkbox-label">Use TLS</span>
</label>
<label>
<input type="checkbox" name="smtp_use_ssl" value="true" {% if settings.get('smtp_use_ssl', False) %}checked{% endif %}>
<span class="checkbox-label">Use SSL</span>
</label>
<button type="submit">Save Email Configuration</button>
</form>
</div>
<div class="section">
<h3>Payment Configuration</h3>
<form method="post">
<h4>Stripe (Credit Cards)</h4>
<label>Publishable Key:</label>
<input type="text" name="stripe_publishable_key" value="{{ settings.get('stripe_publishable_key', '') }}">
<label>Secret Key:</label>
<input type="password" name="stripe_secret_key" placeholder="Enter Stripe secret key">
<h4>PayPal</h4>
<label>Client ID:</label>
<input type="text" name="paypal_client_id" value="{{ settings.get('paypal_client_id', '') }}">
<label>Client Secret:</label>
<input type="password" name="paypal_client_secret" placeholder="Enter PayPal client secret">
<h4>Cryptocurrency</h4>
<label>Bitcoin Address:</label>
<input type="text" name="btc_payment_address" value="{{ settings.get('btc_payment_address', '') }}" placeholder="Enter BTC address">
<label>Ethereum Address:</label>
<input type="text" name="eth_payment_address" value="{{ settings.get('eth_payment_address', '') }}" placeholder="Enter ETH address">
<button type="submit">Save Payment Configuration</button>
</form>
</div>
<div class="section">
<h3>Database Configuration</h3>
<form method="post">
<label>Database Type:</label>
<select name="db_type">
<option value="sqlite" {% if settings.get('db_type', 'sqlite') == 'sqlite' %}selected{% endif %}>SQLite</option>
<option value="mysql" {% if settings.get('db_type', 'sqlite') == 'mysql' %}selected{% endif %}>MySQL</option>
</select>
<div id="sqlite_settings" style="{% if settings.get('db_type', 'sqlite') != 'sqlite' %}display: none;{% endif %}">
<label>SQLite Database Path:</label>
<input type="text" name="db_sqlite_path" value="{{ settings.get('db_sqlite_path', 'vidai.db') }}">
</div>
<div id="mysql_settings" style="{% if settings.get('db_type', 'sqlite') != 'mysql' %}display: none;{% endif %}">
<label>MySQL Host:</label>
<input type="text" name="db_mysql_host" value="{{ settings.get('db_mysql_host', 'localhost') }}">
<label>MySQL Port:</label>
<input type="number" name="db_mysql_port" value="{{ settings.get('db_mysql_port', 3306) }}" min="1" max="65535">
<label>MySQL Username:</label>
<input type="text" name="db_mysql_user" value="{{ settings.get('db_mysql_user', 'vidai') }}">
<label>MySQL Password:</label>
<input type="password" name="db_mysql_password" placeholder="Enter MySQL password">
<label>MySQL Database:</label>
<input type="text" name="db_mysql_database" value="{{ settings.get('db_mysql_database', 'vidai') }}">
<label>MySQL Charset:</label>
<input type="text" name="db_mysql_charset" value="{{ settings.get('db_mysql_charset', 'utf8mb4') }}">
</div>
<button type="submit">Save Database Configuration</button>
</form>
<script>
document.querySelector('select[name="db_type"]').addEventListener('change', function() {
const dbType = this.value;
document.getElementById('sqlite_settings').style.display = dbType === 'sqlite' ? 'block' : 'none';
document.getElementById('mysql_settings').style.display = dbType === 'mysql' ? 'block' : 'none';
});
</script>
</div>
</div> </div>
</body> </body>
</html> </html>
...@@ -994,6 +1305,148 @@ def api_docs(): ...@@ -994,6 +1305,148 @@ def api_docs():
''' '''
return render_template_string(html) return render_template_string(html)
@app.route('/tokens', methods=['GET', 'POST'])
@require_login()
def tokens():
session_id = get_session_id()
user = get_current_user(session_id)
user_tokens = get_user_tokens(user['id'])
token_packages = get_token_packages()
processors = get_available_processors()
message = None
error = None
if request.method == 'POST':
action = request.form.get('action')
if action == 'purchase':
package_tokens = int(request.form.get('tokens', 0))
processor_name = request.form.get('processor', 'stripe')
if package_tokens > 0:
amount = calculate_price(package_tokens)
success, msg, transaction_id = process_payment(user['id'], processor_name, package_tokens, amount)
if success:
message = msg
user_tokens = get_user_tokens(user['id']) # Refresh token count
else:
error = msg
html = f'''
<!DOCTYPE html>
<html>
<head>
<title>Token Management - Video AI</title>
<style>
body {{ font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 20px; }}
.container {{ max-width: 1000px; margin: auto; }}
.header {{ background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; }}
.nav {{ display: flex; gap: 20px; }}
.nav a {{ text-decoration: none; color: #007bff; }}
.token-balance {{ background: #28a745; color: white; padding: 10px 20px; border-radius: 20px; font-weight: bold; }}
.content {{ display: grid; grid-template-columns: 1fr 300px; gap: 20px; }}
.main {{ background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }}
.sidebar {{ background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }}
.packages {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 20px 0; }}
.package {{ border: 2px solid #e0e0e0; padding: 20px; border-radius: 8px; text-align: center; transition: all 0.3s ease; }}
.package:hover {{ border-color: #007bff; transform: translateY(-2px); }}
.package.popular {{ border-color: #28a745; position: relative; }}
.package.popular::before {{ content: 'Most Popular'; position: absolute; top: -10px; left: 50%; transform: translateX(-50%); background: #28a745; color: white; padding: 3px 10px; border-radius: 10px; font-size: 0.8rem; }}
.package h3 {{ margin: 10px 0; }}
.price {{ font-size: 1.5rem; font-weight: bold; color: #007bff; margin: 10px 0; }}
.btn {{ display: inline-block; padding: 8px 16px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; margin: 5px; }}
.btn:hover {{ background: #0056b3; }}
.message {{ padding: 10px; border-radius: 4px; margin: 10px 0; }}
.success {{ background: #d4edda; color: #155724; }}
.error {{ background: #f8d7da; color: #721c24; }}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Token Management</h1>
<div class="nav">
<a href="/">Dashboard</a> |
<a href="/analyze">Analyze</a> |
<a href="/logout">Logout</a>
</div>
</div>
<div class="token-balance">
Current Balance: {user_tokens} tokens
</div>
{% if message %}
<div class="message success">{message}</div>
{% endif %}
{% if error %}
<div class="message error">{error}</div>
{% endif %}
<div class="content">
<div class="main">
<h2>Purchase Tokens</h2>
<div class="packages">
{% for package in token_packages %}
<div class="package {% if package.popular %}popular{% endif %}">
<h3>{package.tokens} Tokens</h3>
<div class="price">${package.price}</div>
<form method="post" style="display: inline;">
<input type="hidden" name="action" value="purchase">
<input type="hidden" name="tokens" value="{package.tokens}">
<select name="processor" style="margin: 5px 0;">
{% for name, proc in processors.items() %}
{% if proc.enabled %}
<option value="{name}">{proc.name}</option>
{% endif %}
{% endfor %}
</select><br>
<button type="submit" class="btn">Purchase</button>
</form>
</div>
{% endfor %}
</div>
<h3>Custom Amount</h3>
<form method="post">
<input type="hidden" name="action" value="purchase">
<input type="number" name="tokens" placeholder="Number of tokens" min="10" required>
<select name="processor">
{% for name, proc in processors.items() %}
{% if proc.enabled %}
<option value="{name}">{proc.name}</option>
{% endif %}
{% endfor %}
</select>
<button type="submit" class="btn">Purchase Custom Amount</button>
</form>
</div>
<div class="sidebar">
<h3>Token Usage</h3>
<p>Tokens are used for video analysis jobs:</p>
<ul>
<li>~10 tokens per minute of video</li>
<li>~50 tokens per image analysis</li>
<li>~200 tokens per training job</li>
</ul>
<h3>Payment Methods</h3>
<ul>
<li><strong>Credit Card:</strong> Instant processing via Stripe</li>
<li><strong>PayPal:</strong> Coming soon</li>
<li><strong>Crypto:</strong> BTC/ETH addresses provided</li>
</ul>
</div>
</div>
</div>
</body>
</html>
'''
return render_template_string(html, user=user, user_tokens=user_tokens, token_packages=token_packages, processors=processors, message=message, error=error)
@app.route('/static/<path:filename>') @app.route('/static/<path:filename>')
def serve_static(filename): def serve_static(filename):
return send_from_directory('static', filename) return send_from_directory('static', filename)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment