添加windows打包逻辑
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -62,9 +62,6 @@ temp/
|
|||||||
tmp/
|
tmp/
|
||||||
.cache/
|
.cache/
|
||||||
|
|
||||||
# Packaging tools
|
|
||||||
packaging/
|
|
||||||
|
|
||||||
# MkDocs build output
|
# MkDocs build output
|
||||||
site/
|
site/
|
||||||
|
|
||||||
|
|||||||
216
packaging/windows/README.md
Normal file
216
packaging/windows/README.md
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
# Windows Package Builder
|
||||||
|
|
||||||
|
Automated build system for creating Windows portable packages of Pixelle-Video.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Python 3.11+ (for running the build script)
|
||||||
|
- PyYAML: `pip install pyyaml`
|
||||||
|
- Internet connection (for downloading Python, FFmpeg, etc.)
|
||||||
|
|
||||||
|
### Build Package
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic build
|
||||||
|
python packaging/windows/build.py
|
||||||
|
|
||||||
|
# Build with China mirrors (faster in China)
|
||||||
|
python packaging/windows/build.py --cn-mirror
|
||||||
|
|
||||||
|
# Custom output directory
|
||||||
|
python packaging/windows/build.py --output /path/to/output
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Edit `config/build_config.yaml` to customize:
|
||||||
|
|
||||||
|
- Python version
|
||||||
|
- FFmpeg version
|
||||||
|
- Excluded files/folders
|
||||||
|
- Build options
|
||||||
|
- Mirror settings
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
The build process creates:
|
||||||
|
|
||||||
|
```
|
||||||
|
dist/windows/
|
||||||
|
├── Pixelle-Video-v0.1.0-win64/ # Build directory
|
||||||
|
│ ├── python/ # Python embedded
|
||||||
|
│ ├── tools/ # FFmpeg, etc.
|
||||||
|
│ ├── Pixelle-Video/ # Project files
|
||||||
|
│ ├── data/ # User data (empty)
|
||||||
|
│ ├── output/ # Output (empty)
|
||||||
|
│ ├── start.bat # Main launcher
|
||||||
|
│ ├── start_api.bat # API launcher
|
||||||
|
│ ├── start_web.bat # Web launcher
|
||||||
|
│ └── README.txt # User guide
|
||||||
|
├── Pixelle-Video-v0.1.0-win64.zip # ZIP package
|
||||||
|
└── Pixelle-Video-v0.1.0-win64.zip.sha256 # Checksum
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Process
|
||||||
|
|
||||||
|
The builder performs these steps:
|
||||||
|
|
||||||
|
1. **Download Phase**
|
||||||
|
- Python embedded distribution
|
||||||
|
- FFmpeg portable
|
||||||
|
- Cached in `.cache/` for reuse
|
||||||
|
|
||||||
|
2. **Extract Phase**
|
||||||
|
- Extract Python to `build/python/`
|
||||||
|
- Extract FFmpeg to `build/tools/ffmpeg/`
|
||||||
|
|
||||||
|
3. **Prepare Phase**
|
||||||
|
- Enable site-packages in Python
|
||||||
|
- Install pip
|
||||||
|
- Install uv (if configured)
|
||||||
|
|
||||||
|
4. **Install Phase**
|
||||||
|
- Install project dependencies using uv/pip
|
||||||
|
- Pre-install all packages
|
||||||
|
|
||||||
|
5. **Copy Phase**
|
||||||
|
- Copy project files (excluding test/docs/cache)
|
||||||
|
- Generate launcher scripts from templates
|
||||||
|
- Create empty directories
|
||||||
|
|
||||||
|
6. **Package Phase**
|
||||||
|
- Create ZIP archive
|
||||||
|
- Generate SHA256 checksum
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
|
||||||
|
Launcher script templates in `templates/`:
|
||||||
|
|
||||||
|
- `start.bat` - Main Web UI launcher
|
||||||
|
- `start_api.bat` - API server launcher
|
||||||
|
- `start_web.bat` - Web UI only launcher
|
||||||
|
- `README.txt` - User documentation
|
||||||
|
|
||||||
|
Templates support placeholders:
|
||||||
|
- `{VERSION}` - Project version
|
||||||
|
- `{BUILD_DATE}` - Build timestamp
|
||||||
|
|
||||||
|
## Cache
|
||||||
|
|
||||||
|
Downloaded files are cached in `.cache/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
.cache/
|
||||||
|
├── python-3.11.9-embed-amd64.zip
|
||||||
|
├── ffmpeg-6.1.1-win64.zip
|
||||||
|
└── get-pip.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Delete cache to force re-download.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Build fails with "PyYAML not found"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install pyyaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Downloads are slow
|
||||||
|
|
||||||
|
Use China mirrors:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python build.py --cn-mirror
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dependencies installation fails
|
||||||
|
|
||||||
|
Check:
|
||||||
|
1. Internet connection
|
||||||
|
2. PyPI mirrors accessibility
|
||||||
|
3. Project dependencies in `pyproject.toml`
|
||||||
|
|
||||||
|
### ZIP creation fails
|
||||||
|
|
||||||
|
Ensure:
|
||||||
|
1. Sufficient disk space
|
||||||
|
2. Write permissions to output directory
|
||||||
|
3. No files are locked by other processes
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
### Custom Configuration
|
||||||
|
|
||||||
|
Create custom config file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp config/build_config.yaml config/my_config.yaml
|
||||||
|
# Edit my_config.yaml
|
||||||
|
python build.py --config config/my_config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Skip ZIP Creation
|
||||||
|
|
||||||
|
Edit `build_config.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
build:
|
||||||
|
create_zip: false
|
||||||
|
```
|
||||||
|
|
||||||
|
### Include Chrome Portable
|
||||||
|
|
||||||
|
Edit `build_config.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
chrome:
|
||||||
|
include: true
|
||||||
|
download_url: "https://path/to/chrome-portable.zip"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Maintenance
|
||||||
|
|
||||||
|
### Update Python Version
|
||||||
|
|
||||||
|
Edit `config/build_config.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
python:
|
||||||
|
version: "3.11.10"
|
||||||
|
download_url: "https://www.python.org/ftp/python/3.11.10/python-3.11.10-embed-amd64.zip"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update FFmpeg Version
|
||||||
|
|
||||||
|
Edit `config/build_config.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
ffmpeg:
|
||||||
|
version: "6.2.0"
|
||||||
|
download_url: "https://github.com/BtbN/FFmpeg-Builds/releases/download/..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Distribution
|
||||||
|
|
||||||
|
To distribute the package:
|
||||||
|
|
||||||
|
1. Upload ZIP file to release page
|
||||||
|
2. Include SHA256 checksum for verification
|
||||||
|
3. Provide installation instructions
|
||||||
|
|
||||||
|
Users verify download:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Windows PowerShell
|
||||||
|
Get-FileHash Pixelle-Video-v0.1.0-win64.zip -Algorithm SHA256
|
||||||
|
```
|
||||||
|
|
||||||
|
Compare with `.sha256` file.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Same as Pixelle-Video project license.
|
||||||
|
|
||||||
690
packaging/windows/build.py
Normal file
690
packaging/windows/build.py
Normal file
@@ -0,0 +1,690 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Windows Package Builder for Pixelle-Video
|
||||||
|
|
||||||
|
This script automates the creation of a Windows portable package:
|
||||||
|
1. Downloads Python embedded distribution
|
||||||
|
2. Downloads FFmpeg portable
|
||||||
|
3. Prepares Python environment (enable site-packages, install pip)
|
||||||
|
4. Installs project dependencies
|
||||||
|
5. Copies project files
|
||||||
|
6. Generates launcher scripts
|
||||||
|
7. Creates final ZIP package
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python build.py [--config CONFIG] [--output OUTPUT] [--cn-mirror]
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
from urllib.request import urlretrieve
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError:
|
||||||
|
print("ERROR: PyYAML is required. Install it with: pip install pyyaml")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
class Color:
|
||||||
|
"""ANSI color codes for terminal output"""
|
||||||
|
HEADER = '\033[95m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
CYAN = '\033[96m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
RESET = '\033[0m'
|
||||||
|
BOLD = '\033[1m'
|
||||||
|
|
||||||
|
|
||||||
|
class WindowsPackageBuilder:
|
||||||
|
"""Build Windows portable package for Pixelle-Video"""
|
||||||
|
|
||||||
|
def __init__(self, config_path: str, output_dir: Optional[str] = None, use_cn_mirror: bool = False):
|
||||||
|
self.config_path = Path(config_path)
|
||||||
|
self.script_dir = Path(__file__).parent
|
||||||
|
self.project_root = self.script_dir.parent.parent
|
||||||
|
|
||||||
|
# Load configuration
|
||||||
|
with open(self.config_path, 'r', encoding='utf-8') as f:
|
||||||
|
self.config = yaml.safe_load(f)
|
||||||
|
|
||||||
|
# Override mirror setting if specified
|
||||||
|
if use_cn_mirror:
|
||||||
|
self.config['mirrors']['use_cn_mirror'] = True
|
||||||
|
|
||||||
|
# Setup paths
|
||||||
|
self.output_dir = Path(output_dir) if output_dir else self.project_root / self.config['build']['output_dir']
|
||||||
|
self.cache_dir = self.project_root / self.config['cache']['cache_dir']
|
||||||
|
self.templates_dir = self.script_dir / 'templates'
|
||||||
|
|
||||||
|
# Get version from pyproject.toml
|
||||||
|
self.version = self._read_version()
|
||||||
|
self.package_name = f"{self.config['package']['name']}-v{self.version}-{self.config['package']['architecture']}"
|
||||||
|
self.build_dir = self.output_dir / self.package_name
|
||||||
|
|
||||||
|
def _read_version(self) -> str:
|
||||||
|
"""Read version from pyproject.toml"""
|
||||||
|
pyproject_path = self.project_root / 'pyproject.toml'
|
||||||
|
try:
|
||||||
|
import tomllib
|
||||||
|
except ImportError:
|
||||||
|
# Python < 3.11 fallback
|
||||||
|
try:
|
||||||
|
import tomli as tomllib
|
||||||
|
except ImportError:
|
||||||
|
# Simple regex fallback
|
||||||
|
import re
|
||||||
|
with open(pyproject_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
return "0.1.0"
|
||||||
|
|
||||||
|
with open(pyproject_path, 'rb') as f:
|
||||||
|
pyproject = tomllib.load(f)
|
||||||
|
return pyproject.get('project', {}).get('version', '0.1.0')
|
||||||
|
|
||||||
|
def log(self, message: str, level: str = "INFO"):
|
||||||
|
"""Print colored log message"""
|
||||||
|
colors = {
|
||||||
|
"INFO": Color.BLUE,
|
||||||
|
"SUCCESS": Color.GREEN,
|
||||||
|
"WARNING": Color.YELLOW,
|
||||||
|
"ERROR": Color.RED,
|
||||||
|
"HEADER": Color.HEADER,
|
||||||
|
}
|
||||||
|
color = colors.get(level, Color.RESET)
|
||||||
|
print(f"{color}[{level}]{Color.RESET} {message}")
|
||||||
|
|
||||||
|
def download_file(self, url: str, output_path: Path, description: str = "", max_retries: int = 3) -> bool:
|
||||||
|
"""Download file with progress indication and retry support"""
|
||||||
|
import ssl
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
try:
|
||||||
|
if attempt > 0:
|
||||||
|
self.log(f"Retry {attempt}/{max_retries}...")
|
||||||
|
|
||||||
|
self.log(f"Downloading {description or url}...")
|
||||||
|
|
||||||
|
# Create SSL context that's more lenient
|
||||||
|
ssl_context = ssl.create_default_context()
|
||||||
|
ssl_context.check_hostname = False
|
||||||
|
ssl_context.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
def report_progress(block_num, block_size, total_size):
|
||||||
|
downloaded = block_num * block_size
|
||||||
|
percent = min(downloaded / total_size * 100, 100) if total_size > 0 else 0
|
||||||
|
print(f"\r Progress: {percent:.1f}%", end='', flush=True)
|
||||||
|
|
||||||
|
# Try with urllib first
|
||||||
|
opener = urllib.request.build_opener(urllib.request.HTTPSHandler(context=ssl_context))
|
||||||
|
urllib.request.install_opener(opener)
|
||||||
|
urlretrieve(url, output_path, reporthook=report_progress)
|
||||||
|
print() # New line after progress
|
||||||
|
self.log(f"Downloaded to {output_path}", "SUCCESS")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"Download attempt {attempt + 1} failed: {e}", "WARNING")
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
import time
|
||||||
|
time.sleep(2) # Wait before retry
|
||||||
|
else:
|
||||||
|
self.log(f"All download attempts failed", "ERROR")
|
||||||
|
# Try with curl as fallback
|
||||||
|
return self._download_with_curl(url, output_path, description)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _find_suitable_python(self) -> Optional[str]:
|
||||||
|
"""Find a suitable Python 3.11+ for installing dependencies"""
|
||||||
|
candidates = [
|
||||||
|
# Try common locations for newer Python versions
|
||||||
|
'/Users/puke/miniforge3/bin/python3', # User's conda
|
||||||
|
'/opt/homebrew/bin/python3', # Homebrew
|
||||||
|
'/usr/local/bin/python3', # Manual install
|
||||||
|
]
|
||||||
|
|
||||||
|
# Also check what's in PATH
|
||||||
|
for i in range(11, 14): # Python 3.11, 3.12, 3.13
|
||||||
|
for py_name in [f'python3.{i}', f'python{i}']:
|
||||||
|
found = shutil.which(py_name)
|
||||||
|
if found and found not in candidates:
|
||||||
|
candidates.append(found)
|
||||||
|
|
||||||
|
# Check generic python3
|
||||||
|
python3_path = shutil.which('python3')
|
||||||
|
if python3_path and '.venv' not in python3_path:
|
||||||
|
candidates.append(python3_path)
|
||||||
|
|
||||||
|
# Test each candidate
|
||||||
|
for candidate in candidates:
|
||||||
|
try:
|
||||||
|
if not candidate:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skip if in project venv
|
||||||
|
if '.venv' in candidate or 'venv' in candidate:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if path exists
|
||||||
|
if not os.path.exists(candidate):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check Python version
|
||||||
|
result = subprocess.run(
|
||||||
|
[candidate, '-c', 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
version = result.stdout.strip()
|
||||||
|
major, minor = map(int, version.split('.'))
|
||||||
|
|
||||||
|
# Need Python 3.11+
|
||||||
|
if major == 3 and minor >= 11:
|
||||||
|
# Check if pip is available
|
||||||
|
pip_check = subprocess.run(
|
||||||
|
[candidate, '-m', 'pip', '--version'],
|
||||||
|
capture_output=True,
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
if pip_check.returncode == 0:
|
||||||
|
self.log(f"Found Python {version} at {candidate}", "SUCCESS")
|
||||||
|
return candidate
|
||||||
|
except Exception as e:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _download_with_curl(self, url: str, output_path: Path, description: str = "") -> bool:
|
||||||
|
"""Fallback download method using curl"""
|
||||||
|
try:
|
||||||
|
self.log(f"Trying curl fallback for {description}...")
|
||||||
|
result = subprocess.run(
|
||||||
|
['curl', '-L', '-o', str(output_path), url, '--progress-bar'],
|
||||||
|
check=True,
|
||||||
|
capture_output=False
|
||||||
|
)
|
||||||
|
if result.returncode == 0 and output_path.exists():
|
||||||
|
self.log(f"Downloaded with curl to {output_path}", "SUCCESS")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"Curl download also failed: {e}", "ERROR")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def download_python(self) -> Path:
|
||||||
|
"""Download Python embedded distribution"""
|
||||||
|
python_config = self.config['python']
|
||||||
|
cache_file = self.cache_dir / f"python-{python_config['version']}-embed-amd64.zip"
|
||||||
|
|
||||||
|
if cache_file.exists():
|
||||||
|
self.log(f"Using cached Python: {cache_file}")
|
||||||
|
return cache_file
|
||||||
|
|
||||||
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Choose URL based on mirror setting
|
||||||
|
url = python_config['mirror_url'] if self.config['mirrors']['use_cn_mirror'] else python_config['download_url']
|
||||||
|
|
||||||
|
if self.download_file(url, cache_file, f"Python {python_config['version']}"):
|
||||||
|
return cache_file
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Failed to download Python")
|
||||||
|
|
||||||
|
def download_ffmpeg(self) -> Path:
|
||||||
|
"""Download FFmpeg portable"""
|
||||||
|
ffmpeg_config = self.config['ffmpeg']
|
||||||
|
cache_file = self.cache_dir / f"ffmpeg-{ffmpeg_config['version']}-win64.zip"
|
||||||
|
|
||||||
|
if cache_file.exists():
|
||||||
|
self.log(f"Using cached FFmpeg: {cache_file}")
|
||||||
|
return cache_file
|
||||||
|
|
||||||
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
url = ffmpeg_config['mirror_url'] if self.config['mirrors']['use_cn_mirror'] else ffmpeg_config['download_url']
|
||||||
|
|
||||||
|
if self.download_file(url, cache_file, f"FFmpeg {ffmpeg_config['version']}"):
|
||||||
|
return cache_file
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Failed to download FFmpeg")
|
||||||
|
|
||||||
|
def extract_python(self, zip_path: Path, target_dir: Path):
|
||||||
|
"""Extract Python embedded distribution"""
|
||||||
|
self.log(f"Extracting Python to {target_dir}...")
|
||||||
|
target_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||||
|
zip_ref.extractall(target_dir)
|
||||||
|
|
||||||
|
# Add execute permissions to .exe files (needed on Unix systems)
|
||||||
|
if os.name != 'nt': # Not on Windows
|
||||||
|
for exe_file in target_dir.glob('*.exe'):
|
||||||
|
os.chmod(exe_file, 0o755)
|
||||||
|
for exe_file in target_dir.glob('**/*.exe'):
|
||||||
|
os.chmod(exe_file, 0o755)
|
||||||
|
|
||||||
|
self.log("Python extracted successfully", "SUCCESS")
|
||||||
|
|
||||||
|
def extract_ffmpeg(self, zip_path: Path, target_dir: Path):
|
||||||
|
"""Extract FFmpeg portable"""
|
||||||
|
self.log(f"Extracting FFmpeg to {target_dir}...")
|
||||||
|
temp_extract = target_dir.parent / "ffmpeg_temp"
|
||||||
|
temp_extract.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||||
|
zip_ref.extractall(temp_extract)
|
||||||
|
|
||||||
|
# Find the bin directory (FFmpeg archive has nested structure)
|
||||||
|
bin_dir = None
|
||||||
|
for root, dirs, files in os.walk(temp_extract):
|
||||||
|
if 'bin' in dirs:
|
||||||
|
bin_dir = Path(root) / 'bin'
|
||||||
|
break
|
||||||
|
|
||||||
|
if bin_dir and bin_dir.exists():
|
||||||
|
target_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copytree(bin_dir, target_dir, dirs_exist_ok=True)
|
||||||
|
shutil.rmtree(temp_extract)
|
||||||
|
self.log("FFmpeg extracted successfully", "SUCCESS")
|
||||||
|
else:
|
||||||
|
raise RuntimeError("FFmpeg bin directory not found in archive")
|
||||||
|
|
||||||
|
def prepare_python_environment(self, python_dir: Path):
|
||||||
|
"""Prepare Python environment: enable site-packages"""
|
||||||
|
self.log("Preparing Python environment...")
|
||||||
|
|
||||||
|
# Modify python311._pth to enable site-packages
|
||||||
|
pth_file = python_dir / "python311._pth"
|
||||||
|
if pth_file.exists():
|
||||||
|
with open(pth_file, 'r') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
# Uncomment "import site" line or add it
|
||||||
|
modified = False
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.strip().startswith('#import site'):
|
||||||
|
lines[i] = 'import site\n'
|
||||||
|
modified = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not modified and 'import site' not in ''.join(lines):
|
||||||
|
lines.append('import site\n')
|
||||||
|
|
||||||
|
with open(pth_file, 'w') as f:
|
||||||
|
f.writelines(lines)
|
||||||
|
|
||||||
|
self.log("Enabled site-packages in Python", "SUCCESS")
|
||||||
|
|
||||||
|
# Note: On non-Windows systems, we can't run python.exe directly
|
||||||
|
# Pip and dependencies will be installed using system Python
|
||||||
|
if os.name == 'nt':
|
||||||
|
# On Windows, we can install pip directly
|
||||||
|
python_exe = python_dir / "python.exe"
|
||||||
|
get_pip_path = self.cache_dir / "get-pip.py"
|
||||||
|
|
||||||
|
if not get_pip_path.exists():
|
||||||
|
self.log("Downloading get-pip.py...")
|
||||||
|
pip_url = "https://bootstrap.pypa.io/get-pip.py"
|
||||||
|
self.download_file(pip_url, get_pip_path, "get-pip.py")
|
||||||
|
|
||||||
|
self.log("Installing pip...")
|
||||||
|
result = subprocess.run(
|
||||||
|
[str(python_exe), str(get_pip_path)],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
self.log("Pip installed successfully", "SUCCESS")
|
||||||
|
else:
|
||||||
|
self.log(f"Pip installation warning: {result.stderr}", "WARNING")
|
||||||
|
else:
|
||||||
|
self.log("Cross-platform build detected (building on non-Windows)", "INFO")
|
||||||
|
self.log("Dependencies will be installed using system Python", "INFO")
|
||||||
|
|
||||||
|
def install_dependencies(self, python_dir: Path):
|
||||||
|
"""Install project dependencies"""
|
||||||
|
self.log("Installing project dependencies...")
|
||||||
|
|
||||||
|
# Determine target directory for site-packages
|
||||||
|
site_packages = python_dir / "Lib" / "site-packages"
|
||||||
|
site_packages.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
if os.name == 'nt':
|
||||||
|
# On Windows, use the embedded Python
|
||||||
|
python_exe = python_dir / "python.exe"
|
||||||
|
|
||||||
|
# Install uv first if configured
|
||||||
|
if self.config['build'].get('use_uv', True):
|
||||||
|
self.log("Installing uv...")
|
||||||
|
subprocess.run(
|
||||||
|
[str(python_exe), "-m", "pip", "install", "uv"],
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
if self.config['build'].get('use_uv', True):
|
||||||
|
cmd = [str(python_exe), "-m", "uv", "pip", "install", "-e", str(self.project_root)]
|
||||||
|
if self.config['mirrors']['use_cn_mirror']:
|
||||||
|
cmd.extend(["--index-url", self.config['mirrors']['pypi_mirror']])
|
||||||
|
else:
|
||||||
|
cmd = [str(python_exe), "-m", "pip", "install", "-e", str(self.project_root)]
|
||||||
|
if self.config['mirrors']['use_cn_mirror']:
|
||||||
|
cmd.extend(["--index-url", self.config['mirrors']['pypi_mirror']])
|
||||||
|
|
||||||
|
self.log(f"Running: {' '.join(cmd)}")
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
self.log("Dependencies installed successfully", "SUCCESS")
|
||||||
|
else:
|
||||||
|
self.log(f"Dependency installation failed:\n{result.stderr}", "ERROR")
|
||||||
|
raise RuntimeError("Failed to install dependencies")
|
||||||
|
else:
|
||||||
|
# Cross-platform build: use system Python to install to target directory
|
||||||
|
self.log("Cross-platform build: using system Python to install dependencies")
|
||||||
|
|
||||||
|
# Find a Python 3.11+ executable (not from project venv)
|
||||||
|
python_cmd = self._find_suitable_python()
|
||||||
|
|
||||||
|
if not python_cmd:
|
||||||
|
self.log("No suitable Python 3.11+ found. Please install Python 3.11+ or use Windows to build.", "ERROR")
|
||||||
|
raise RuntimeError("Python 3.11+ required for cross-platform build")
|
||||||
|
|
||||||
|
self.log(f"Using Python: {python_cmd}")
|
||||||
|
|
||||||
|
# Use pip with --target to install to specific directory
|
||||||
|
cmd = [
|
||||||
|
python_cmd, "-m", "pip", "install",
|
||||||
|
"--target", str(site_packages),
|
||||||
|
"--no-user",
|
||||||
|
"--no-warn-script-location"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Read dependencies from pyproject.toml
|
||||||
|
try:
|
||||||
|
import tomllib
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
import tomli as tomllib
|
||||||
|
except ImportError:
|
||||||
|
self.log("tomllib/tomli not available, trying simple parsing", "WARNING")
|
||||||
|
tomllib = None
|
||||||
|
|
||||||
|
if tomllib:
|
||||||
|
pyproject_path = self.project_root / "pyproject.toml"
|
||||||
|
with open(pyproject_path, 'rb') as f:
|
||||||
|
pyproject = tomllib.load(f)
|
||||||
|
deps = pyproject.get('project', {}).get('dependencies', [])
|
||||||
|
else:
|
||||||
|
# Simple fallback: read from pyproject.toml manually
|
||||||
|
import re
|
||||||
|
pyproject_path = self.project_root / "pyproject.toml"
|
||||||
|
with open(pyproject_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
# Find dependencies section
|
||||||
|
deps_match = re.search(r'dependencies\s*=\s*\[(.*?)\]', content, re.DOTALL)
|
||||||
|
if deps_match:
|
||||||
|
deps_str = deps_match.group(1)
|
||||||
|
deps = [dep.strip(' "\',\n') for dep in deps_str.split('\n') if dep.strip() and not dep.strip().startswith('#')]
|
||||||
|
else:
|
||||||
|
deps = []
|
||||||
|
|
||||||
|
if deps:
|
||||||
|
cmd.extend(deps)
|
||||||
|
|
||||||
|
if self.config['mirrors']['use_cn_mirror']:
|
||||||
|
cmd.extend(["--index-url", self.config['mirrors']['pypi_mirror']])
|
||||||
|
|
||||||
|
self.log(f"Installing {len(deps)} dependencies...")
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
self.log("Dependencies installed successfully", "SUCCESS")
|
||||||
|
else:
|
||||||
|
self.log(f"Dependency installation output:\n{result.stdout}", "INFO")
|
||||||
|
if result.stderr:
|
||||||
|
self.log(f"Warnings: {result.stderr}", "WARNING")
|
||||||
|
else:
|
||||||
|
self.log("No dependencies found in pyproject.toml", "WARNING")
|
||||||
|
|
||||||
|
def copy_project_files(self, target_dir: Path):
|
||||||
|
"""Copy project files to build directory"""
|
||||||
|
self.log(f"Copying project files to {target_dir}...")
|
||||||
|
|
||||||
|
exclude_patterns = self.config['build']['exclude_patterns']
|
||||||
|
|
||||||
|
def should_exclude(path: Path) -> bool:
|
||||||
|
path_str = str(path.relative_to(self.project_root))
|
||||||
|
for pattern in exclude_patterns:
|
||||||
|
if pattern.endswith('/*'):
|
||||||
|
# Directory content exclusion
|
||||||
|
if path_str.startswith(pattern[:-2]):
|
||||||
|
return True
|
||||||
|
elif pattern.endswith('*'):
|
||||||
|
# Wildcard pattern
|
||||||
|
if path_str.startswith(pattern[:-1]):
|
||||||
|
return True
|
||||||
|
elif '*' in pattern:
|
||||||
|
# Glob pattern (simple check)
|
||||||
|
import fnmatch
|
||||||
|
if fnmatch.fnmatch(path_str, pattern):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# Exact match or directory
|
||||||
|
if path_str == pattern or path_str.startswith(f"{pattern}/"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
target_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Copy files
|
||||||
|
copied_count = 0
|
||||||
|
for item in self.project_root.iterdir():
|
||||||
|
if item.name in ['.git', 'packaging', 'dist', '.venv', 'venv']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if should_exclude(item):
|
||||||
|
continue
|
||||||
|
|
||||||
|
target_path = target_dir / item.name
|
||||||
|
|
||||||
|
if item.is_file():
|
||||||
|
shutil.copy2(item, target_path)
|
||||||
|
copied_count += 1
|
||||||
|
elif item.is_dir():
|
||||||
|
shutil.copytree(item, target_path, ignore=lambda d, names: [
|
||||||
|
n for n in names if should_exclude(Path(d) / n)
|
||||||
|
])
|
||||||
|
# Count files in copied directory
|
||||||
|
copied_count += sum(1 for _ in target_path.rglob('*') if _.is_file())
|
||||||
|
|
||||||
|
self.log(f"Copied {copied_count} files", "SUCCESS")
|
||||||
|
|
||||||
|
def generate_launcher_scripts(self):
|
||||||
|
"""Generate launcher scripts from templates"""
|
||||||
|
self.log("Generating launcher scripts...")
|
||||||
|
|
||||||
|
replacements = {
|
||||||
|
'{VERSION}': self.version,
|
||||||
|
'{BUILD_DATE}': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy and process templates
|
||||||
|
for template_file in self.templates_dir.glob('*'):
|
||||||
|
if template_file.is_file():
|
||||||
|
target_file = self.build_dir / template_file.name
|
||||||
|
|
||||||
|
with open(template_file, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Replace placeholders
|
||||||
|
for key, value in replacements.items():
|
||||||
|
content = content.replace(key, value)
|
||||||
|
|
||||||
|
with open(target_file, 'w', encoding='utf-8', newline='\r\n') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
self.log(f"Generated: {template_file.name}")
|
||||||
|
|
||||||
|
self.log("Launcher scripts generated", "SUCCESS")
|
||||||
|
|
||||||
|
def create_empty_directories(self):
|
||||||
|
"""Create empty directories specified in config"""
|
||||||
|
self.log("Creating empty directories...")
|
||||||
|
|
||||||
|
for dir_name in self.config['build'].get('create_empty_dirs', []):
|
||||||
|
dir_path = self.build_dir / dir_name
|
||||||
|
dir_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
# Create .gitkeep to preserve directory in git
|
||||||
|
(dir_path / '.gitkeep').touch()
|
||||||
|
|
||||||
|
self.log("Empty directories created", "SUCCESS")
|
||||||
|
|
||||||
|
def create_zip_package(self):
|
||||||
|
"""Create final ZIP package"""
|
||||||
|
if not self.config['build'].get('create_zip', True):
|
||||||
|
return
|
||||||
|
|
||||||
|
zip_path = self.output_dir / f"{self.package_name}.zip"
|
||||||
|
self.log(f"Creating ZIP package: {zip_path}...")
|
||||||
|
|
||||||
|
compression_map = {
|
||||||
|
'deflate': zipfile.ZIP_DEFLATED,
|
||||||
|
'bzip2': zipfile.ZIP_BZIP2,
|
||||||
|
'lzma': zipfile.ZIP_LZMA,
|
||||||
|
}
|
||||||
|
compression = compression_map.get(
|
||||||
|
self.config['build'].get('zip_compression', 'deflate'),
|
||||||
|
zipfile.ZIP_DEFLATED
|
||||||
|
)
|
||||||
|
|
||||||
|
with zipfile.ZipFile(zip_path, 'w', compression) as zipf:
|
||||||
|
for root, dirs, files in os.walk(self.build_dir):
|
||||||
|
for file in files:
|
||||||
|
file_path = Path(root) / file
|
||||||
|
arcname = file_path.relative_to(self.build_dir.parent)
|
||||||
|
zipf.write(file_path, arcname)
|
||||||
|
|
||||||
|
# Calculate file size and hash
|
||||||
|
size_mb = zip_path.stat().st_size / (1024 * 1024)
|
||||||
|
|
||||||
|
with open(zip_path, 'rb') as f:
|
||||||
|
file_hash = hashlib.sha256(f.read()).hexdigest()
|
||||||
|
|
||||||
|
self.log(f"ZIP package created: {zip_path}", "SUCCESS")
|
||||||
|
self.log(f"Size: {size_mb:.2f} MB")
|
||||||
|
self.log(f"SHA256: {file_hash}")
|
||||||
|
|
||||||
|
# Write hash to file
|
||||||
|
hash_file = zip_path.with_suffix('.zip.sha256')
|
||||||
|
with open(hash_file, 'w') as f:
|
||||||
|
f.write(f"{file_hash} {zip_path.name}\n")
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
"""Main build process"""
|
||||||
|
self.log("=" * 60, "HEADER")
|
||||||
|
self.log(f"Building {self.package_name}", "HEADER")
|
||||||
|
self.log("=" * 60, "HEADER")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Clean build directory
|
||||||
|
if self.build_dir.exists():
|
||||||
|
self.log(f"Cleaning existing build directory: {self.build_dir}")
|
||||||
|
shutil.rmtree(self.build_dir)
|
||||||
|
|
||||||
|
self.build_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Download dependencies
|
||||||
|
python_zip = self.download_python()
|
||||||
|
ffmpeg_zip = self.download_ffmpeg()
|
||||||
|
|
||||||
|
# Extract Python
|
||||||
|
python_dir = self.build_dir / "python" / "python311"
|
||||||
|
self.extract_python(python_zip, python_dir)
|
||||||
|
|
||||||
|
# Extract FFmpeg
|
||||||
|
ffmpeg_dir = self.build_dir / "tools" / "ffmpeg" / "bin"
|
||||||
|
self.extract_ffmpeg(ffmpeg_zip, ffmpeg_dir)
|
||||||
|
|
||||||
|
# Prepare Python environment
|
||||||
|
self.prepare_python_environment(python_dir)
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
if self.config['build'].get('pre_install_deps', True):
|
||||||
|
self.install_dependencies(python_dir)
|
||||||
|
|
||||||
|
# Copy project files
|
||||||
|
project_target = self.build_dir / "Pixelle-Video"
|
||||||
|
self.copy_project_files(project_target)
|
||||||
|
|
||||||
|
# Generate launcher scripts
|
||||||
|
self.generate_launcher_scripts()
|
||||||
|
|
||||||
|
# Create empty directories
|
||||||
|
self.create_empty_directories()
|
||||||
|
|
||||||
|
# Create ZIP package
|
||||||
|
self.create_zip_package()
|
||||||
|
|
||||||
|
self.log("=" * 60, "HEADER")
|
||||||
|
self.log("Build completed successfully!", "SUCCESS")
|
||||||
|
self.log(f"Package location: {self.build_dir}", "SUCCESS")
|
||||||
|
self.log("=" * 60, "HEADER")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log(f"Build failed: {e}", "ERROR")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Build Windows portable package for Pixelle-Video")
|
||||||
|
parser.add_argument(
|
||||||
|
'--config',
|
||||||
|
default='packaging/windows/config/build_config.yaml',
|
||||||
|
help='Path to build configuration file'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--output',
|
||||||
|
help='Output directory (default: dist/windows)'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--cn-mirror',
|
||||||
|
action='store_true',
|
||||||
|
help='Use China mirrors for faster downloads'
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
builder = WindowsPackageBuilder(
|
||||||
|
config_path=args.config,
|
||||||
|
output_dir=args.output,
|
||||||
|
use_cn_mirror=args.cn_mirror
|
||||||
|
)
|
||||||
|
builder.build()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
78
packaging/windows/config/build_config.yaml
Normal file
78
packaging/windows/config/build_config.yaml
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Windows Package Build Configuration
|
||||||
|
|
||||||
|
# Package information
|
||||||
|
package:
|
||||||
|
name: Pixelle-Video
|
||||||
|
version_source: pyproject.toml # Read version from pyproject.toml
|
||||||
|
architecture: win64
|
||||||
|
|
||||||
|
# Python configuration
|
||||||
|
python:
|
||||||
|
version: "3.11.9"
|
||||||
|
download_url: "https://www.python.org/ftp/python/3.11.9/python-3.11.9-embed-amd64.zip"
|
||||||
|
# Mirror for China users (optional)
|
||||||
|
mirror_url: "https://mirrors.huaweicloud.com/python/3.11.9/python-3.11.9-embed-amd64.zip"
|
||||||
|
|
||||||
|
# FFmpeg configuration
|
||||||
|
ffmpeg:
|
||||||
|
version: "6.1.1"
|
||||||
|
download_url: "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip"
|
||||||
|
# Alternative mirror
|
||||||
|
mirror_url: "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip"
|
||||||
|
|
||||||
|
# Chrome/Chromium configuration (for html2image)
|
||||||
|
chrome:
|
||||||
|
include: false # Set to true to bundle Chrome portable
|
||||||
|
version: "120.0.6099.109"
|
||||||
|
download_url: "" # Add portable Chrome download URL if needed
|
||||||
|
note: "Users can use system Chrome or install separately"
|
||||||
|
|
||||||
|
# Build options
|
||||||
|
build:
|
||||||
|
# Files/folders to exclude from project copy
|
||||||
|
exclude_patterns:
|
||||||
|
- ".git"
|
||||||
|
- ".github"
|
||||||
|
- "__pycache__"
|
||||||
|
- "*.pyc"
|
||||||
|
- ".pytest_cache"
|
||||||
|
- ".ruff_cache"
|
||||||
|
- "*.log"
|
||||||
|
- ".DS_Store"
|
||||||
|
- "output/*" # Don't include output files
|
||||||
|
- "temp/*"
|
||||||
|
- "plans/*" # Don't include planning docs
|
||||||
|
- "repositories/*" # Don't include referenced repos
|
||||||
|
- "docs/*" # Don't include docs in package
|
||||||
|
- "test_*.py" # Don't include test files
|
||||||
|
- ".venv"
|
||||||
|
- "venv"
|
||||||
|
- "node_modules"
|
||||||
|
- "uv.lock"
|
||||||
|
|
||||||
|
# Dependencies installation
|
||||||
|
use_uv: true # Use uv for faster dependency installation
|
||||||
|
pre_install_deps: true # Install deps during build (recommended for end users)
|
||||||
|
|
||||||
|
# Output
|
||||||
|
output_dir: "dist/windows"
|
||||||
|
create_zip: true
|
||||||
|
zip_compression: "deflate" # deflate, bzip2, lzma
|
||||||
|
|
||||||
|
# Additional options
|
||||||
|
include_readme: true
|
||||||
|
include_license: true
|
||||||
|
create_empty_dirs:
|
||||||
|
- "data"
|
||||||
|
- "output"
|
||||||
|
|
||||||
|
# Download cache
|
||||||
|
cache:
|
||||||
|
enabled: true
|
||||||
|
cache_dir: "packaging/windows/.cache"
|
||||||
|
|
||||||
|
# Mirror settings (for China users)
|
||||||
|
mirrors:
|
||||||
|
use_cn_mirror: false # Set to true for faster downloads in China
|
||||||
|
pypi_mirror: "https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||||
|
|
||||||
5
packaging/windows/requirements.txt
Normal file
5
packaging/windows/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Requirements for building Windows package
|
||||||
|
# Install with: pip install -r requirements.txt
|
||||||
|
|
||||||
|
pyyaml>=6.0.0
|
||||||
|
|
||||||
110
packaging/windows/templates/README.txt
Normal file
110
packaging/windows/templates/README.txt
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
========================================
|
||||||
|
Pixelle-Video - Windows Portable
|
||||||
|
========================================
|
||||||
|
|
||||||
|
AI-powered video creation platform
|
||||||
|
|
||||||
|
Version: {VERSION}
|
||||||
|
Build Date: {BUILD_DATE}
|
||||||
|
|
||||||
|
========================================
|
||||||
|
Quick Start
|
||||||
|
========================================
|
||||||
|
|
||||||
|
1. Double-click "start.bat" to launch the Web UI
|
||||||
|
2. Configure your API keys in the Web UI (Settings section)
|
||||||
|
3. Open your browser at: http://localhost:8501
|
||||||
|
|
||||||
|
========================================
|
||||||
|
Available Launchers
|
||||||
|
========================================
|
||||||
|
|
||||||
|
start.bat - Launch Web UI (Default)
|
||||||
|
start_api.bat - Launch API Server only (Port 8000)
|
||||||
|
start_web.bat - Launch Web UI only (Port 8501)
|
||||||
|
|
||||||
|
For most users, just use "start.bat"
|
||||||
|
|
||||||
|
========================================
|
||||||
|
First-Time Setup
|
||||||
|
========================================
|
||||||
|
|
||||||
|
1. On first run, the Web UI will start with default configuration
|
||||||
|
2. Click on "Settings" in the Web UI to configure:
|
||||||
|
- LLM API Key (OpenAI/Qwen/DeepSeek/etc)
|
||||||
|
- LLM Base URL and Model
|
||||||
|
- ComfyUI settings (use RunningHub or local ComfyUI)
|
||||||
|
3. Click "Save Config" to save your settings
|
||||||
|
4. Configuration will be automatically saved to config.yaml
|
||||||
|
|
||||||
|
========================================
|
||||||
|
Configuration
|
||||||
|
========================================
|
||||||
|
|
||||||
|
Configuration is done through the Web UI:
|
||||||
|
|
||||||
|
1. Launch the application using start.bat
|
||||||
|
2. Click on "Settings" in the Web UI
|
||||||
|
3. Fill in the required fields:
|
||||||
|
- LLM API Key: Your LLM provider API key
|
||||||
|
- LLM Base URL: LLM API endpoint
|
||||||
|
- LLM Model: Model name (e.g., gpt-4o, qwen-max)
|
||||||
|
- ComfyUI URL: For local ComfyUI (default: http://127.0.0.1:8188)
|
||||||
|
- RunningHub API Key: For cloud image generation (optional)
|
||||||
|
4. Click "Save Config" to save
|
||||||
|
|
||||||
|
The configuration will be automatically saved to Pixelle-Video/config.yaml.
|
||||||
|
|
||||||
|
Note: You can also manually edit config.yaml if needed, but the Web UI is recommended.
|
||||||
|
|
||||||
|
========================================
|
||||||
|
Folder Structure
|
||||||
|
========================================
|
||||||
|
|
||||||
|
python/ - Python 3.11 embedded runtime
|
||||||
|
tools/ - FFmpeg and other utilities
|
||||||
|
Pixelle-Video/ - Main application
|
||||||
|
data/ - User data (BGM, templates, workflows)
|
||||||
|
output/ - Generated videos
|
||||||
|
|
||||||
|
========================================
|
||||||
|
System Requirements
|
||||||
|
========================================
|
||||||
|
|
||||||
|
- Windows 10/11 (64-bit)
|
||||||
|
- 4GB RAM minimum (8GB recommended)
|
||||||
|
- Internet connection (for API calls and ComfyUI cloud)
|
||||||
|
- Modern web browser (Chrome/Edge/Firefox)
|
||||||
|
|
||||||
|
========================================
|
||||||
|
Troubleshooting
|
||||||
|
========================================
|
||||||
|
|
||||||
|
Problem: "Python not found"
|
||||||
|
Solution: Ensure python/ folder exists and is not corrupted
|
||||||
|
|
||||||
|
Problem: "Failed to start"
|
||||||
|
Solution: Check if Python and dependencies are installed correctly
|
||||||
|
|
||||||
|
Problem: "Port already in use"
|
||||||
|
Solution: Close other applications using port 8501 or 8000
|
||||||
|
|
||||||
|
Problem: "Module not found"
|
||||||
|
Solution: Re-extract the package completely, don't move files
|
||||||
|
|
||||||
|
========================================
|
||||||
|
Support
|
||||||
|
========================================
|
||||||
|
|
||||||
|
GitHub: https://github.com/AIDC-AI/Pixelle-Video
|
||||||
|
Documentation: https://pixelle.ai/docs
|
||||||
|
Issues: https://github.com/AIDC-AI/Pixelle-Video/issues
|
||||||
|
|
||||||
|
========================================
|
||||||
|
License
|
||||||
|
========================================
|
||||||
|
|
||||||
|
See LICENSE file in Pixelle-Video/ folder
|
||||||
|
|
||||||
|
Copyright (c) 2025 Pixelle.AI
|
||||||
|
|
||||||
40
packaging/windows/templates/start.bat
Normal file
40
packaging/windows/templates/start.bat
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
echo ========================================
|
||||||
|
echo Pixelle-Video - Windows Launcher
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
:: Set environment variables
|
||||||
|
set "PYTHON_HOME=%~dp0python\python311"
|
||||||
|
set "PATH=%PYTHON_HOME%;%PYTHON_HOME%\Scripts;%~dp0tools\ffmpeg\bin;%PATH%"
|
||||||
|
set "PROJECT_ROOT=%~dp0Pixelle-Video"
|
||||||
|
|
||||||
|
:: Change to project directory
|
||||||
|
cd /d "%PROJECT_ROOT%"
|
||||||
|
|
||||||
|
:: Set PYTHONPATH to project root for module imports
|
||||||
|
set "PYTHONPATH=%PROJECT_ROOT%"
|
||||||
|
|
||||||
|
:: Start Web UI
|
||||||
|
echo [Starting] Launching Pixelle-Video Web UI...
|
||||||
|
echo Browser will open at: http://localhost:8501
|
||||||
|
echo.
|
||||||
|
echo Note: Configure API keys and settings in the Web UI.
|
||||||
|
echo Press Ctrl+C to stop the server
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
"%PYTHON_HOME%\python.exe" -m streamlit run web\app.py
|
||||||
|
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo.
|
||||||
|
echo [ERROR] Failed to start. Please check:
|
||||||
|
echo 1. Python is properly installed
|
||||||
|
echo 2. Dependencies are installed
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
)
|
||||||
|
|
||||||
42
packaging/windows/templates/start_api.bat
Normal file
42
packaging/windows/templates/start_api.bat
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
echo ========================================
|
||||||
|
echo Pixelle-Video - API Server Launcher
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
:: Set environment variables
|
||||||
|
set "PYTHON_HOME=%~dp0python\python311"
|
||||||
|
set "PATH=%PYTHON_HOME%;%PYTHON_HOME%\Scripts;%~dp0tools\ffmpeg\bin;%PATH%"
|
||||||
|
set "PROJECT_ROOT=%~dp0Pixelle-Video"
|
||||||
|
|
||||||
|
:: Change to project directory
|
||||||
|
cd /d "%PROJECT_ROOT%"
|
||||||
|
|
||||||
|
:: Set PYTHONPATH to project root for module imports
|
||||||
|
set "PYTHONPATH=%PROJECT_ROOT%"
|
||||||
|
|
||||||
|
:: Start API Server
|
||||||
|
echo [Starting] Launching Pixelle-Video API Server...
|
||||||
|
echo API will be available at: http://localhost:8000
|
||||||
|
echo API Documentation: http://localhost:8000/docs
|
||||||
|
echo.
|
||||||
|
echo Note: Configure API keys and settings in the Web UI.
|
||||||
|
echo Press Ctrl+C to stop the server
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
"%PYTHON_HOME%\python.exe" -m uvicorn api.app:app --host 0.0.0.0 --port 8000
|
||||||
|
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo.
|
||||||
|
echo [ERROR] Failed to start. Please check:
|
||||||
|
echo 1. Python is properly installed
|
||||||
|
echo 2. Dependencies are installed
|
||||||
|
echo 3. Port 8000 is not already in use
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
)
|
||||||
|
|
||||||
44
packaging/windows/templates/start_web.bat
Normal file
44
packaging/windows/templates/start_web.bat
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
echo ========================================
|
||||||
|
echo Pixelle-Video - Web UI Launcher
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
:: Set environment variables
|
||||||
|
set "PYTHON_HOME=%~dp0python\python311"
|
||||||
|
set "PATH=%PYTHON_HOME%;%PYTHON_HOME%\Scripts;%~dp0tools\ffmpeg\bin;%PATH%"
|
||||||
|
set "PROJECT_ROOT=%~dp0Pixelle-Video"
|
||||||
|
|
||||||
|
:: Change to project directory
|
||||||
|
cd /d "%PROJECT_ROOT%"
|
||||||
|
|
||||||
|
:: Set PYTHONPATH to project root for module imports
|
||||||
|
set "PYTHONPATH=%PROJECT_ROOT%"
|
||||||
|
|
||||||
|
:: Start Web UI (Standalone mode)
|
||||||
|
echo [Starting] Launching Pixelle-Video Web UI (Standalone)...
|
||||||
|
echo Browser will open at: http://localhost:8501
|
||||||
|
echo.
|
||||||
|
echo NOTE: This runs Web UI only. For full features, use start.bat
|
||||||
|
echo or run start_api.bat in another window.
|
||||||
|
echo Note: Configure API keys and settings in the Web UI.
|
||||||
|
echo.
|
||||||
|
echo Press Ctrl+C to stop the server
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
"%PYTHON_HOME%\python.exe" -m streamlit run web\app.py
|
||||||
|
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo.
|
||||||
|
echo [ERROR] Failed to start. Please check:
|
||||||
|
echo 1. Python is properly installed
|
||||||
|
echo 2. Dependencies are installed
|
||||||
|
echo 3. Port 8501 is not already in use
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
)
|
||||||
|
|
||||||
Reference in New Issue
Block a user