Spaces:
Runtime error
Runtime error
new
#1
by naicoi - opened
This view is limited to 50 files because it contains too many changes. See the raw diff here.
- .agents/skills/uv-package-manager/SKILL.md +0 -834
- .claude/skills/uv-package-manager +0 -1
- .dockerignore +0 -38
- .github/workflows/docker.yml +0 -57
- .gitignore +0 -21
- .opencode/skills/uv-package-manager +0 -1
- .python-version +0 -1
- AGENTS.md +0 -226
- Dockerfile +0 -48
- ORIGINAL_README.md +0 -106
- README.md +5 -156
- app.py +0 -189
- assets/readme.md +0 -0
- audio_processing.py +0 -211
- checkpoints/readme.md +0 -0
- config.py +0 -25
- configs/inference/musetalk.yaml +0 -42
- configs/scheduler_config.json +0 -12
- configs/unet/stage2_512.yaml +0 -99
- docker-compose.yml +0 -19
- download_checkpoints.sh +0 -18
- download_musetalk_models.py +0 -203
- eval/detectors/README.md +0 -3
- eval/detectors/__init__.py +0 -1
- eval/detectors/s3fd/__init__.py +0 -63
- eval/detectors/s3fd/box_utils.py +0 -221
- eval/detectors/s3fd/nets.py +0 -174
- eval/draw_syncnet_lines.py +0 -64
- eval/eval_fvd.py +0 -98
- eval/eval_sync_conf.py +0 -77
- eval/eval_sync_conf.sh +0 -2
- eval/eval_syncnet_acc.py +0 -137
- eval/eval_syncnet_acc.sh +0 -3
- eval/fvd.py +0 -58
- eval/hyper_iqa.py +0 -343
- eval/inference_videos.py +0 -77
- eval/syncnet/__init__.py +0 -1
- eval/syncnet/syncnet.py +0 -113
- eval/syncnet/syncnet_eval.py +0 -220
- eval/syncnet_detect.py +0 -251
- face_processing.py +0 -585
- latentsync/data/syncnet_dataset.py +0 -139
- latentsync/data/unet_dataset.py +0 -152
- latentsync/models/attention.py +0 -280
- latentsync/models/motion_module.py +0 -313
- latentsync/models/resnet.py +0 -228
- latentsync/models/stable_syncnet.py +0 -233
- latentsync/models/unet.py +0 -512
- latentsync/models/unet_blocks.py +0 -777
- latentsync/models/utils.py +0 -19
.agents/skills/uv-package-manager/SKILL.md
DELETED
|
@@ -1,834 +0,0 @@
|
|
| 1 |
-
---
|
| 2 |
-
name: uv-package-manager
|
| 3 |
-
description: Master the uv package manager for fast Python dependency management, virtual environments, and modern Python project workflows. Use when setting up Python projects, managing dependencies, or optimizing Python development workflows with uv.
|
| 4 |
-
---
|
| 5 |
-
|
| 6 |
-
# UV Package Manager
|
| 7 |
-
|
| 8 |
-
Comprehensive guide to using uv, an extremely fast Python package installer and resolver written in Rust, for modern Python project management and dependency workflows.
|
| 9 |
-
|
| 10 |
-
## When to Use This Skill
|
| 11 |
-
|
| 12 |
-
- Setting up new Python projects quickly
|
| 13 |
-
- Managing Python dependencies faster than pip
|
| 14 |
-
- Creating and managing virtual environments
|
| 15 |
-
- Installing Python interpreters
|
| 16 |
-
- Resolving dependency conflicts efficiently
|
| 17 |
-
- Migrating from pip/pip-tools/poetry
|
| 18 |
-
- Speeding up CI/CD pipelines
|
| 19 |
-
- Managing monorepo Python projects
|
| 20 |
-
- Working with lockfiles for reproducible builds
|
| 21 |
-
- Optimizing Docker builds with Python dependencies
|
| 22 |
-
|
| 23 |
-
## Core Concepts
|
| 24 |
-
|
| 25 |
-
### 1. What is uv?
|
| 26 |
-
|
| 27 |
-
- **Ultra-fast package installer**: 10-100x faster than pip
|
| 28 |
-
- **Written in Rust**: Leverages Rust's performance
|
| 29 |
-
- **Drop-in pip replacement**: Compatible with pip workflows
|
| 30 |
-
- **Virtual environment manager**: Create and manage venvs
|
| 31 |
-
- **Python installer**: Download and manage Python versions
|
| 32 |
-
- **Resolver**: Advanced dependency resolution
|
| 33 |
-
- **Lockfile support**: Reproducible installations
|
| 34 |
-
|
| 35 |
-
### 2. Key Features
|
| 36 |
-
|
| 37 |
-
- Blazing fast installation speeds
|
| 38 |
-
- Disk space efficient with global cache
|
| 39 |
-
- Compatible with pip, pip-tools, poetry
|
| 40 |
-
- Comprehensive dependency resolution
|
| 41 |
-
- Cross-platform support (Linux, macOS, Windows)
|
| 42 |
-
- No Python required for installation
|
| 43 |
-
- Built-in virtual environment support
|
| 44 |
-
|
| 45 |
-
### 3. UV vs Traditional Tools
|
| 46 |
-
|
| 47 |
-
- **vs pip**: 10-100x faster, better resolver
|
| 48 |
-
- **vs pip-tools**: Faster, simpler, better UX
|
| 49 |
-
- **vs poetry**: Faster, less opinionated, lighter
|
| 50 |
-
- **vs conda**: Faster, Python-focused
|
| 51 |
-
|
| 52 |
-
## Installation
|
| 53 |
-
|
| 54 |
-
### Quick Install
|
| 55 |
-
|
| 56 |
-
```bash
|
| 57 |
-
# macOS/Linux
|
| 58 |
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 59 |
-
|
| 60 |
-
# Windows (PowerShell)
|
| 61 |
-
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
|
| 62 |
-
|
| 63 |
-
# Using pip (if you already have Python)
|
| 64 |
-
pip install uv
|
| 65 |
-
|
| 66 |
-
# Using Homebrew (macOS)
|
| 67 |
-
brew install uv
|
| 68 |
-
|
| 69 |
-
# Using cargo (if you have Rust)
|
| 70 |
-
cargo install --git https://github.com/astral-sh/uv uv
|
| 71 |
-
```
|
| 72 |
-
|
| 73 |
-
### Verify Installation
|
| 74 |
-
|
| 75 |
-
```bash
|
| 76 |
-
uv --version
|
| 77 |
-
# uv 0.x.x
|
| 78 |
-
```
|
| 79 |
-
|
| 80 |
-
## Quick Start
|
| 81 |
-
|
| 82 |
-
### Create a New Project
|
| 83 |
-
|
| 84 |
-
```bash
|
| 85 |
-
# Create new project with virtual environment
|
| 86 |
-
uv init my-project
|
| 87 |
-
cd my-project
|
| 88 |
-
|
| 89 |
-
# Or create in current directory
|
| 90 |
-
uv init .
|
| 91 |
-
|
| 92 |
-
# Initialize creates:
|
| 93 |
-
# - .python-version (Python version)
|
| 94 |
-
# - pyproject.toml (project config)
|
| 95 |
-
# - README.md
|
| 96 |
-
# - .gitignore
|
| 97 |
-
```
|
| 98 |
-
|
| 99 |
-
### Install Dependencies
|
| 100 |
-
|
| 101 |
-
```bash
|
| 102 |
-
# Install packages (creates venv if needed)
|
| 103 |
-
uv add requests pandas
|
| 104 |
-
|
| 105 |
-
# Install dev dependencies
|
| 106 |
-
uv add --dev pytest black ruff
|
| 107 |
-
|
| 108 |
-
# Install from requirements.txt
|
| 109 |
-
uv pip install -r requirements.txt
|
| 110 |
-
|
| 111 |
-
# Install from pyproject.toml
|
| 112 |
-
uv sync
|
| 113 |
-
```
|
| 114 |
-
|
| 115 |
-
## Virtual Environment Management
|
| 116 |
-
|
| 117 |
-
### Pattern 1: Creating Virtual Environments
|
| 118 |
-
|
| 119 |
-
```bash
|
| 120 |
-
# Create virtual environment with uv
|
| 121 |
-
uv venv
|
| 122 |
-
|
| 123 |
-
# Create with specific Python version
|
| 124 |
-
uv venv --python 3.12
|
| 125 |
-
|
| 126 |
-
# Create with custom name
|
| 127 |
-
uv venv my-env
|
| 128 |
-
|
| 129 |
-
# Create with system site packages
|
| 130 |
-
uv venv --system-site-packages
|
| 131 |
-
|
| 132 |
-
# Specify location
|
| 133 |
-
uv venv /path/to/venv
|
| 134 |
-
```
|
| 135 |
-
|
| 136 |
-
### Pattern 2: Activating Virtual Environments
|
| 137 |
-
|
| 138 |
-
```bash
|
| 139 |
-
# Linux/macOS
|
| 140 |
-
source .venv/bin/activate
|
| 141 |
-
|
| 142 |
-
# Windows (Command Prompt)
|
| 143 |
-
.venv\Scripts\activate.bat
|
| 144 |
-
|
| 145 |
-
# Windows (PowerShell)
|
| 146 |
-
.venv\Scripts\Activate.ps1
|
| 147 |
-
|
| 148 |
-
# Or use uv run (no activation needed)
|
| 149 |
-
uv run python script.py
|
| 150 |
-
uv run pytest
|
| 151 |
-
```
|
| 152 |
-
|
| 153 |
-
### Pattern 3: Using uv run
|
| 154 |
-
|
| 155 |
-
```bash
|
| 156 |
-
# Run Python script (auto-activates venv)
|
| 157 |
-
uv run python app.py
|
| 158 |
-
|
| 159 |
-
# Run installed CLI tool
|
| 160 |
-
uv run black .
|
| 161 |
-
uv run pytest
|
| 162 |
-
|
| 163 |
-
# Run with specific Python version
|
| 164 |
-
uv run --python 3.11 python script.py
|
| 165 |
-
|
| 166 |
-
# Pass arguments
|
| 167 |
-
uv run python script.py --arg value
|
| 168 |
-
```
|
| 169 |
-
|
| 170 |
-
## Package Management
|
| 171 |
-
|
| 172 |
-
### Pattern 4: Adding Dependencies
|
| 173 |
-
|
| 174 |
-
```bash
|
| 175 |
-
# Add package (adds to pyproject.toml)
|
| 176 |
-
uv add requests
|
| 177 |
-
|
| 178 |
-
# Add with version constraint
|
| 179 |
-
uv add "django>=4.0,<5.0"
|
| 180 |
-
|
| 181 |
-
# Add multiple packages
|
| 182 |
-
uv add numpy pandas matplotlib
|
| 183 |
-
|
| 184 |
-
# Add dev dependency
|
| 185 |
-
uv add --dev pytest pytest-cov
|
| 186 |
-
|
| 187 |
-
# Add optional dependency group
|
| 188 |
-
uv add --optional docs sphinx
|
| 189 |
-
|
| 190 |
-
# Add from git
|
| 191 |
-
uv add git+https://github.com/user/repo.git
|
| 192 |
-
|
| 193 |
-
# Add from git with specific ref
|
| 194 |
-
uv add git+https://github.com/user/repo.git@v1.0.0
|
| 195 |
-
|
| 196 |
-
# Add from local path
|
| 197 |
-
uv add ./local-package
|
| 198 |
-
|
| 199 |
-
# Add editable local package
|
| 200 |
-
uv add -e ./local-package
|
| 201 |
-
```
|
| 202 |
-
|
| 203 |
-
### Pattern 5: Removing Dependencies
|
| 204 |
-
|
| 205 |
-
```bash
|
| 206 |
-
# Remove package
|
| 207 |
-
uv remove requests
|
| 208 |
-
|
| 209 |
-
# Remove dev dependency
|
| 210 |
-
uv remove --dev pytest
|
| 211 |
-
|
| 212 |
-
# Remove multiple packages
|
| 213 |
-
uv remove numpy pandas matplotlib
|
| 214 |
-
```
|
| 215 |
-
|
| 216 |
-
### Pattern 6: Upgrading Dependencies
|
| 217 |
-
|
| 218 |
-
```bash
|
| 219 |
-
# Upgrade specific package
|
| 220 |
-
uv add --upgrade requests
|
| 221 |
-
|
| 222 |
-
# Upgrade all packages
|
| 223 |
-
uv sync --upgrade
|
| 224 |
-
|
| 225 |
-
# Upgrade package to latest
|
| 226 |
-
uv add --upgrade requests
|
| 227 |
-
|
| 228 |
-
# Show what would be upgraded
|
| 229 |
-
uv tree --outdated
|
| 230 |
-
```
|
| 231 |
-
|
| 232 |
-
### Pattern 7: Locking Dependencies
|
| 233 |
-
|
| 234 |
-
```bash
|
| 235 |
-
# Generate uv.lock file
|
| 236 |
-
uv lock
|
| 237 |
-
|
| 238 |
-
# Update lock file
|
| 239 |
-
uv lock --upgrade
|
| 240 |
-
|
| 241 |
-
# Lock without installing
|
| 242 |
-
uv lock --no-install
|
| 243 |
-
|
| 244 |
-
# Lock specific package
|
| 245 |
-
uv lock --upgrade-package requests
|
| 246 |
-
```
|
| 247 |
-
|
| 248 |
-
## Python Version Management
|
| 249 |
-
|
| 250 |
-
### Pattern 8: Installing Python Versions
|
| 251 |
-
|
| 252 |
-
```bash
|
| 253 |
-
# Install Python version
|
| 254 |
-
uv python install 3.12
|
| 255 |
-
|
| 256 |
-
# Install multiple versions
|
| 257 |
-
uv python install 3.11 3.12 3.13
|
| 258 |
-
|
| 259 |
-
# Install latest version
|
| 260 |
-
uv python install
|
| 261 |
-
|
| 262 |
-
# List installed versions
|
| 263 |
-
uv python list
|
| 264 |
-
|
| 265 |
-
# Find available versions
|
| 266 |
-
uv python list --all-versions
|
| 267 |
-
```
|
| 268 |
-
|
| 269 |
-
### Pattern 9: Setting Python Version
|
| 270 |
-
|
| 271 |
-
```bash
|
| 272 |
-
# Set Python version for project
|
| 273 |
-
uv python pin 3.12
|
| 274 |
-
|
| 275 |
-
# This creates/updates .python-version file
|
| 276 |
-
|
| 277 |
-
# Use specific Python version for command
|
| 278 |
-
uv --python 3.11 run python script.py
|
| 279 |
-
|
| 280 |
-
# Create venv with specific version
|
| 281 |
-
uv venv --python 3.12
|
| 282 |
-
```
|
| 283 |
-
|
| 284 |
-
## Project Configuration
|
| 285 |
-
|
| 286 |
-
### Pattern 10: pyproject.toml with uv
|
| 287 |
-
|
| 288 |
-
```toml
|
| 289 |
-
[project]
|
| 290 |
-
name = "my-project"
|
| 291 |
-
version = "0.1.0"
|
| 292 |
-
description = "My awesome project"
|
| 293 |
-
readme = "README.md"
|
| 294 |
-
requires-python = ">=3.8"
|
| 295 |
-
dependencies = [
|
| 296 |
-
"requests>=2.31.0",
|
| 297 |
-
"pydantic>=2.0.0",
|
| 298 |
-
"click>=8.1.0",
|
| 299 |
-
]
|
| 300 |
-
|
| 301 |
-
[project.optional-dependencies]
|
| 302 |
-
dev = [
|
| 303 |
-
"pytest>=7.4.0",
|
| 304 |
-
"pytest-cov>=4.1.0",
|
| 305 |
-
"black>=23.0.0",
|
| 306 |
-
"ruff>=0.1.0",
|
| 307 |
-
"mypy>=1.5.0",
|
| 308 |
-
]
|
| 309 |
-
docs = [
|
| 310 |
-
"sphinx>=7.0.0",
|
| 311 |
-
"sphinx-rtd-theme>=1.3.0",
|
| 312 |
-
]
|
| 313 |
-
|
| 314 |
-
[build-system]
|
| 315 |
-
requires = ["hatchling"]
|
| 316 |
-
build-backend = "hatchling.build"
|
| 317 |
-
|
| 318 |
-
[tool.uv]
|
| 319 |
-
dev-dependencies = [
|
| 320 |
-
# Additional dev dependencies managed by uv
|
| 321 |
-
]
|
| 322 |
-
|
| 323 |
-
[tool.uv.sources]
|
| 324 |
-
# Custom package sources
|
| 325 |
-
my-package = { git = "https://github.com/user/repo.git" }
|
| 326 |
-
```
|
| 327 |
-
|
| 328 |
-
### Pattern 11: Using uv with Existing Projects
|
| 329 |
-
|
| 330 |
-
```bash
|
| 331 |
-
# Migrate from requirements.txt
|
| 332 |
-
uv add -r requirements.txt
|
| 333 |
-
|
| 334 |
-
# Migrate from poetry
|
| 335 |
-
# Already have pyproject.toml, just use:
|
| 336 |
-
uv sync
|
| 337 |
-
|
| 338 |
-
# Export to requirements.txt
|
| 339 |
-
uv pip freeze > requirements.txt
|
| 340 |
-
|
| 341 |
-
# Export with hashes
|
| 342 |
-
uv pip freeze --require-hashes > requirements.txt
|
| 343 |
-
```
|
| 344 |
-
|
| 345 |
-
## Advanced Workflows
|
| 346 |
-
|
| 347 |
-
### Pattern 12: Monorepo Support
|
| 348 |
-
|
| 349 |
-
```bash
|
| 350 |
-
# Project structure
|
| 351 |
-
# monorepo/
|
| 352 |
-
# packages/
|
| 353 |
-
# package-a/
|
| 354 |
-
# pyproject.toml
|
| 355 |
-
# package-b/
|
| 356 |
-
# pyproject.toml
|
| 357 |
-
# pyproject.toml (root)
|
| 358 |
-
|
| 359 |
-
# Root pyproject.toml
|
| 360 |
-
[tool.uv.workspace]
|
| 361 |
-
members = ["packages/*"]
|
| 362 |
-
|
| 363 |
-
# Install all workspace packages
|
| 364 |
-
uv sync
|
| 365 |
-
|
| 366 |
-
# Add workspace dependency
|
| 367 |
-
uv add --path ./packages/package-a
|
| 368 |
-
```
|
| 369 |
-
|
| 370 |
-
### Pattern 13: CI/CD Integration
|
| 371 |
-
|
| 372 |
-
```yaml
|
| 373 |
-
# .github/workflows/test.yml
|
| 374 |
-
name: Tests
|
| 375 |
-
|
| 376 |
-
on: [push, pull_request]
|
| 377 |
-
|
| 378 |
-
jobs:
|
| 379 |
-
test:
|
| 380 |
-
runs-on: ubuntu-latest
|
| 381 |
-
|
| 382 |
-
steps:
|
| 383 |
-
- uses: actions/checkout@v4
|
| 384 |
-
|
| 385 |
-
- name: Install uv
|
| 386 |
-
uses: astral-sh/setup-uv@v2
|
| 387 |
-
with:
|
| 388 |
-
enable-cache: true
|
| 389 |
-
|
| 390 |
-
- name: Set up Python
|
| 391 |
-
run: uv python install 3.12
|
| 392 |
-
|
| 393 |
-
- name: Install dependencies
|
| 394 |
-
run: uv sync --all-extras --dev
|
| 395 |
-
|
| 396 |
-
- name: Run tests
|
| 397 |
-
run: uv run pytest
|
| 398 |
-
|
| 399 |
-
- name: Run linting
|
| 400 |
-
run: |
|
| 401 |
-
uv run ruff check .
|
| 402 |
-
uv run black --check .
|
| 403 |
-
```
|
| 404 |
-
|
| 405 |
-
### Pattern 14: Docker Integration
|
| 406 |
-
|
| 407 |
-
```dockerfile
|
| 408 |
-
# Dockerfile
|
| 409 |
-
FROM python:3.12-slim
|
| 410 |
-
|
| 411 |
-
# Install uv
|
| 412 |
-
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
| 413 |
-
|
| 414 |
-
# Set working directory
|
| 415 |
-
WORKDIR /app
|
| 416 |
-
|
| 417 |
-
# Copy dependency files
|
| 418 |
-
COPY pyproject.toml uv.lock ./
|
| 419 |
-
|
| 420 |
-
# Install dependencies
|
| 421 |
-
RUN uv sync --frozen --no-dev
|
| 422 |
-
|
| 423 |
-
# Copy application code
|
| 424 |
-
COPY . .
|
| 425 |
-
|
| 426 |
-
# Run application
|
| 427 |
-
CMD ["uv", "run", "python", "app.py"]
|
| 428 |
-
```
|
| 429 |
-
|
| 430 |
-
**Optimized multi-stage build:**
|
| 431 |
-
|
| 432 |
-
```dockerfile
|
| 433 |
-
# Multi-stage Dockerfile
|
| 434 |
-
FROM python:3.12-slim AS builder
|
| 435 |
-
|
| 436 |
-
# Install uv
|
| 437 |
-
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
| 438 |
-
|
| 439 |
-
WORKDIR /app
|
| 440 |
-
|
| 441 |
-
# Install dependencies to venv
|
| 442 |
-
COPY pyproject.toml uv.lock ./
|
| 443 |
-
RUN uv sync --frozen --no-dev --no-editable
|
| 444 |
-
|
| 445 |
-
# Runtime stage
|
| 446 |
-
FROM python:3.12-slim
|
| 447 |
-
|
| 448 |
-
WORKDIR /app
|
| 449 |
-
|
| 450 |
-
# Copy venv from builder
|
| 451 |
-
COPY --from=builder /app/.venv .venv
|
| 452 |
-
COPY . .
|
| 453 |
-
|
| 454 |
-
# Use venv
|
| 455 |
-
ENV PATH="/app/.venv/bin:$PATH"
|
| 456 |
-
|
| 457 |
-
CMD ["python", "app.py"]
|
| 458 |
-
```
|
| 459 |
-
|
| 460 |
-
### Pattern 15: Lockfile Workflows
|
| 461 |
-
|
| 462 |
-
```bash
|
| 463 |
-
# Create lockfile (uv.lock)
|
| 464 |
-
uv lock
|
| 465 |
-
|
| 466 |
-
# Install from lockfile (exact versions)
|
| 467 |
-
uv sync --frozen
|
| 468 |
-
|
| 469 |
-
# Update lockfile without installing
|
| 470 |
-
uv lock --no-install
|
| 471 |
-
|
| 472 |
-
# Upgrade specific package in lock
|
| 473 |
-
uv lock --upgrade-package requests
|
| 474 |
-
|
| 475 |
-
# Check if lockfile is up to date
|
| 476 |
-
uv lock --check
|
| 477 |
-
|
| 478 |
-
# Export lockfile to requirements.txt
|
| 479 |
-
uv export --format requirements-txt > requirements.txt
|
| 480 |
-
|
| 481 |
-
# Export with hashes for security
|
| 482 |
-
uv export --format requirements-txt --hash > requirements.txt
|
| 483 |
-
```
|
| 484 |
-
|
| 485 |
-
## Performance Optimization
|
| 486 |
-
|
| 487 |
-
### Pattern 16: Using Global Cache
|
| 488 |
-
|
| 489 |
-
```bash
|
| 490 |
-
# UV automatically uses global cache at:
|
| 491 |
-
# Linux: ~/.cache/uv
|
| 492 |
-
# macOS: ~/Library/Caches/uv
|
| 493 |
-
# Windows: %LOCALAPPDATA%\uv\cache
|
| 494 |
-
|
| 495 |
-
# Clear cache
|
| 496 |
-
uv cache clean
|
| 497 |
-
|
| 498 |
-
# Check cache size
|
| 499 |
-
uv cache dir
|
| 500 |
-
```
|
| 501 |
-
|
| 502 |
-
### Pattern 17: Parallel Installation
|
| 503 |
-
|
| 504 |
-
```bash
|
| 505 |
-
# UV installs packages in parallel by default
|
| 506 |
-
|
| 507 |
-
# Control parallelism
|
| 508 |
-
uv pip install --jobs 4 package1 package2
|
| 509 |
-
|
| 510 |
-
# No parallel (sequential)
|
| 511 |
-
uv pip install --jobs 1 package
|
| 512 |
-
```
|
| 513 |
-
|
| 514 |
-
### Pattern 18: Offline Mode
|
| 515 |
-
|
| 516 |
-
```bash
|
| 517 |
-
# Install from cache only (no network)
|
| 518 |
-
uv pip install --offline package
|
| 519 |
-
|
| 520 |
-
# Sync from lockfile offline
|
| 521 |
-
uv sync --frozen --offline
|
| 522 |
-
```
|
| 523 |
-
|
| 524 |
-
## Comparison with Other Tools
|
| 525 |
-
|
| 526 |
-
### uv vs pip
|
| 527 |
-
|
| 528 |
-
```bash
|
| 529 |
-
# pip
|
| 530 |
-
python -m venv .venv
|
| 531 |
-
source .venv/bin/activate
|
| 532 |
-
pip install requests pandas numpy
|
| 533 |
-
# ~30 seconds
|
| 534 |
-
|
| 535 |
-
# uv
|
| 536 |
-
uv venv
|
| 537 |
-
uv add requests pandas numpy
|
| 538 |
-
# ~2 seconds (10-15x faster)
|
| 539 |
-
```
|
| 540 |
-
|
| 541 |
-
### uv vs poetry
|
| 542 |
-
|
| 543 |
-
```bash
|
| 544 |
-
# poetry
|
| 545 |
-
poetry init
|
| 546 |
-
poetry add requests pandas
|
| 547 |
-
poetry install
|
| 548 |
-
# ~20 seconds
|
| 549 |
-
|
| 550 |
-
# uv
|
| 551 |
-
uv init
|
| 552 |
-
uv add requests pandas
|
| 553 |
-
uv sync
|
| 554 |
-
# ~3 seconds (6-7x faster)
|
| 555 |
-
```
|
| 556 |
-
|
| 557 |
-
### uv vs pip-tools
|
| 558 |
-
|
| 559 |
-
```bash
|
| 560 |
-
# pip-tools
|
| 561 |
-
pip-compile requirements.in
|
| 562 |
-
pip-sync requirements.txt
|
| 563 |
-
# ~15 seconds
|
| 564 |
-
|
| 565 |
-
# uv
|
| 566 |
-
uv lock
|
| 567 |
-
uv sync --frozen
|
| 568 |
-
# ~2 seconds (7-8x faster)
|
| 569 |
-
```
|
| 570 |
-
|
| 571 |
-
## Common Workflows
|
| 572 |
-
|
| 573 |
-
### Pattern 19: Starting a New Project
|
| 574 |
-
|
| 575 |
-
```bash
|
| 576 |
-
# Complete workflow
|
| 577 |
-
uv init my-project
|
| 578 |
-
cd my-project
|
| 579 |
-
|
| 580 |
-
# Set Python version
|
| 581 |
-
uv python pin 3.12
|
| 582 |
-
|
| 583 |
-
# Add dependencies
|
| 584 |
-
uv add fastapi uvicorn pydantic
|
| 585 |
-
|
| 586 |
-
# Add dev dependencies
|
| 587 |
-
uv add --dev pytest black ruff mypy
|
| 588 |
-
|
| 589 |
-
# Create structure
|
| 590 |
-
mkdir -p src/my_project tests
|
| 591 |
-
|
| 592 |
-
# Run tests
|
| 593 |
-
uv run pytest
|
| 594 |
-
|
| 595 |
-
# Format code
|
| 596 |
-
uv run black .
|
| 597 |
-
uv run ruff check .
|
| 598 |
-
```
|
| 599 |
-
|
| 600 |
-
### Pattern 20: Maintaining Existing Project
|
| 601 |
-
|
| 602 |
-
```bash
|
| 603 |
-
# Clone repository
|
| 604 |
-
git clone https://github.com/user/project.git
|
| 605 |
-
cd project
|
| 606 |
-
|
| 607 |
-
# Install dependencies (creates venv automatically)
|
| 608 |
-
uv sync
|
| 609 |
-
|
| 610 |
-
# Install with dev dependencies
|
| 611 |
-
uv sync --all-extras
|
| 612 |
-
|
| 613 |
-
# Update dependencies
|
| 614 |
-
uv lock --upgrade
|
| 615 |
-
|
| 616 |
-
# Run application
|
| 617 |
-
uv run python app.py
|
| 618 |
-
|
| 619 |
-
# Run tests
|
| 620 |
-
uv run pytest
|
| 621 |
-
|
| 622 |
-
# Add new dependency
|
| 623 |
-
uv add new-package
|
| 624 |
-
|
| 625 |
-
# Commit updated files
|
| 626 |
-
git add pyproject.toml uv.lock
|
| 627 |
-
git commit -m "Add new-package dependency"
|
| 628 |
-
```
|
| 629 |
-
|
| 630 |
-
## Tool Integration
|
| 631 |
-
|
| 632 |
-
### Pattern 21: Pre-commit Hooks
|
| 633 |
-
|
| 634 |
-
```yaml
|
| 635 |
-
# .pre-commit-config.yaml
|
| 636 |
-
repos:
|
| 637 |
-
- repo: local
|
| 638 |
-
hooks:
|
| 639 |
-
- id: uv-lock
|
| 640 |
-
name: uv lock
|
| 641 |
-
entry: uv lock
|
| 642 |
-
language: system
|
| 643 |
-
pass_filenames: false
|
| 644 |
-
|
| 645 |
-
- id: ruff
|
| 646 |
-
name: ruff
|
| 647 |
-
entry: uv run ruff check --fix
|
| 648 |
-
language: system
|
| 649 |
-
types: [python]
|
| 650 |
-
|
| 651 |
-
- id: black
|
| 652 |
-
name: black
|
| 653 |
-
entry: uv run black
|
| 654 |
-
language: system
|
| 655 |
-
types: [python]
|
| 656 |
-
```
|
| 657 |
-
|
| 658 |
-
### Pattern 22: VS Code Integration
|
| 659 |
-
|
| 660 |
-
```json
|
| 661 |
-
// .vscode/settings.json
|
| 662 |
-
{
|
| 663 |
-
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
|
| 664 |
-
"python.terminal.activateEnvironment": true,
|
| 665 |
-
"python.testing.pytestEnabled": true,
|
| 666 |
-
"python.testing.pytestArgs": ["-v"],
|
| 667 |
-
"python.linting.enabled": true,
|
| 668 |
-
"python.formatting.provider": "black",
|
| 669 |
-
"[python]": {
|
| 670 |
-
"editor.defaultFormatter": "ms-python.black-formatter",
|
| 671 |
-
"editor.formatOnSave": true
|
| 672 |
-
}
|
| 673 |
-
}
|
| 674 |
-
```
|
| 675 |
-
|
| 676 |
-
## Troubleshooting
|
| 677 |
-
|
| 678 |
-
### Common Issues
|
| 679 |
-
|
| 680 |
-
```bash
|
| 681 |
-
# Issue: uv not found
|
| 682 |
-
# Solution: Add to PATH or reinstall
|
| 683 |
-
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.bashrc
|
| 684 |
-
|
| 685 |
-
# Issue: Wrong Python version
|
| 686 |
-
# Solution: Pin version explicitly
|
| 687 |
-
uv python pin 3.12
|
| 688 |
-
uv venv --python 3.12
|
| 689 |
-
|
| 690 |
-
# Issue: Dependency conflict
|
| 691 |
-
# Solution: Check resolution
|
| 692 |
-
uv lock --verbose
|
| 693 |
-
|
| 694 |
-
# Issue: Cache issues
|
| 695 |
-
# Solution: Clear cache
|
| 696 |
-
uv cache clean
|
| 697 |
-
|
| 698 |
-
# Issue: Lockfile out of sync
|
| 699 |
-
# Solution: Regenerate
|
| 700 |
-
uv lock --upgrade
|
| 701 |
-
```
|
| 702 |
-
|
| 703 |
-
## Best Practices
|
| 704 |
-
|
| 705 |
-
### Project Setup
|
| 706 |
-
|
| 707 |
-
1. **Always use lockfiles** for reproducibility
|
| 708 |
-
2. **Pin Python version** with .python-version
|
| 709 |
-
3. **Separate dev dependencies** from production
|
| 710 |
-
4. **Use uv run** instead of activating venv
|
| 711 |
-
5. **Commit uv.lock** to version control
|
| 712 |
-
6. **Use --frozen in CI** for consistent builds
|
| 713 |
-
7. **Leverage global cache** for speed
|
| 714 |
-
8. **Use workspace** for monorepos
|
| 715 |
-
9. **Export requirements.txt** for compatibility
|
| 716 |
-
10. **Keep uv updated** for latest features
|
| 717 |
-
|
| 718 |
-
### Performance Tips
|
| 719 |
-
|
| 720 |
-
```bash
|
| 721 |
-
# Use frozen installs in CI
|
| 722 |
-
uv sync --frozen
|
| 723 |
-
|
| 724 |
-
# Use offline mode when possible
|
| 725 |
-
uv sync --offline
|
| 726 |
-
|
| 727 |
-
# Parallel operations (automatic)
|
| 728 |
-
# uv does this by default
|
| 729 |
-
|
| 730 |
-
# Reuse cache across environments
|
| 731 |
-
# uv shares cache globally
|
| 732 |
-
|
| 733 |
-
# Use lockfiles to skip resolution
|
| 734 |
-
uv sync --frozen # skips resolution
|
| 735 |
-
```
|
| 736 |
-
|
| 737 |
-
## Migration Guide
|
| 738 |
-
|
| 739 |
-
### From pip + requirements.txt
|
| 740 |
-
|
| 741 |
-
```bash
|
| 742 |
-
# Before
|
| 743 |
-
python -m venv .venv
|
| 744 |
-
source .venv/bin/activate
|
| 745 |
-
pip install -r requirements.txt
|
| 746 |
-
|
| 747 |
-
# After
|
| 748 |
-
uv venv
|
| 749 |
-
uv pip install -r requirements.txt
|
| 750 |
-
# Or better:
|
| 751 |
-
uv init
|
| 752 |
-
uv add -r requirements.txt
|
| 753 |
-
```
|
| 754 |
-
|
| 755 |
-
### From Poetry
|
| 756 |
-
|
| 757 |
-
```bash
|
| 758 |
-
# Before
|
| 759 |
-
poetry install
|
| 760 |
-
poetry add requests
|
| 761 |
-
|
| 762 |
-
# After
|
| 763 |
-
uv sync
|
| 764 |
-
uv add requests
|
| 765 |
-
|
| 766 |
-
# Keep existing pyproject.toml
|
| 767 |
-
# uv reads [project] and [tool.poetry] sections
|
| 768 |
-
```
|
| 769 |
-
|
| 770 |
-
### From pip-tools
|
| 771 |
-
|
| 772 |
-
```bash
|
| 773 |
-
# Before
|
| 774 |
-
pip-compile requirements.in
|
| 775 |
-
pip-sync requirements.txt
|
| 776 |
-
|
| 777 |
-
# After
|
| 778 |
-
uv lock
|
| 779 |
-
uv sync --frozen
|
| 780 |
-
```
|
| 781 |
-
|
| 782 |
-
## Command Reference
|
| 783 |
-
|
| 784 |
-
### Essential Commands
|
| 785 |
-
|
| 786 |
-
```bash
|
| 787 |
-
# Project management
|
| 788 |
-
uv init [PATH] # Initialize project
|
| 789 |
-
uv add PACKAGE # Add dependency
|
| 790 |
-
uv remove PACKAGE # Remove dependency
|
| 791 |
-
uv sync # Install dependencies
|
| 792 |
-
uv lock # Create/update lockfile
|
| 793 |
-
|
| 794 |
-
# Virtual environments
|
| 795 |
-
uv venv [PATH] # Create venv
|
| 796 |
-
uv run COMMAND # Run in venv
|
| 797 |
-
|
| 798 |
-
# Python management
|
| 799 |
-
uv python install VERSION # Install Python
|
| 800 |
-
uv python list # List installed Pythons
|
| 801 |
-
uv python pin VERSION # Pin Python version
|
| 802 |
-
|
| 803 |
-
# Package installation (pip-compatible)
|
| 804 |
-
uv pip install PACKAGE # Install package
|
| 805 |
-
uv pip uninstall PACKAGE # Uninstall package
|
| 806 |
-
uv pip freeze # List installed
|
| 807 |
-
uv pip list # List packages
|
| 808 |
-
|
| 809 |
-
# Utility
|
| 810 |
-
uv cache clean # Clear cache
|
| 811 |
-
uv cache dir # Show cache location
|
| 812 |
-
uv --version # Show version
|
| 813 |
-
```
|
| 814 |
-
|
| 815 |
-
## Resources
|
| 816 |
-
|
| 817 |
-
- **Official documentation**: https://docs.astral.sh/uv/
|
| 818 |
-
- **GitHub repository**: https://github.com/astral-sh/uv
|
| 819 |
-
- **Astral blog**: https://astral.sh/blog
|
| 820 |
-
- **Migration guides**: https://docs.astral.sh/uv/guides/
|
| 821 |
-
- **Comparison with other tools**: https://docs.astral.sh/uv/pip/compatibility/
|
| 822 |
-
|
| 823 |
-
## Best Practices Summary
|
| 824 |
-
|
| 825 |
-
1. **Use uv for all new projects** - Start with `uv init`
|
| 826 |
-
2. **Commit lockfiles** - Ensure reproducible builds
|
| 827 |
-
3. **Pin Python versions** - Use .python-version
|
| 828 |
-
4. **Use uv run** - Avoid manual venv activation
|
| 829 |
-
5. **Leverage caching** - Let uv manage global cache
|
| 830 |
-
6. **Use --frozen in CI** - Exact reproduction
|
| 831 |
-
7. **Keep uv updated** - Fast-moving project
|
| 832 |
-
8. **Use workspaces** - For monorepo projects
|
| 833 |
-
9. **Export for compatibility** - Generate requirements.txt when needed
|
| 834 |
-
10. **Read the docs** - uv is feature-rich and evolving
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.claude/skills/uv-package-manager
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
../../.agents/skills/uv-package-manager
|
|
|
|
|
|
.dockerignore
DELETED
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
# Python
|
| 2 |
-
__pycache__/
|
| 3 |
-
*.py[cod]
|
| 4 |
-
*$py.class
|
| 5 |
-
*.so
|
| 6 |
-
.Python
|
| 7 |
-
*.egg-info/
|
| 8 |
-
dist/
|
| 9 |
-
build/
|
| 10 |
-
|
| 11 |
-
# Virtual Environment
|
| 12 |
-
.venv/
|
| 13 |
-
venv/
|
| 14 |
-
|
| 15 |
-
# Cache
|
| 16 |
-
.cache/
|
| 17 |
-
*.log
|
| 18 |
-
*.tmp
|
| 19 |
-
|
| 20 |
-
# Output directories
|
| 21 |
-
processed_results/
|
| 22 |
-
|
| 23 |
-
# IDE
|
| 24 |
-
.vscode/
|
| 25 |
-
.idea/
|
| 26 |
-
*.swp
|
| 27 |
-
|
| 28 |
-
# Git
|
| 29 |
-
.git/
|
| 30 |
-
.gitignore
|
| 31 |
-
|
| 32 |
-
# Documentation
|
| 33 |
-
*.md
|
| 34 |
-
!README.md
|
| 35 |
-
|
| 36 |
-
# Test
|
| 37 |
-
tests/
|
| 38 |
-
eval/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.github/workflows/docker.yml
DELETED
|
@@ -1,57 +0,0 @@
|
|
| 1 |
-
name: Build and Push to GHCR
|
| 2 |
-
|
| 3 |
-
on:
|
| 4 |
-
push:
|
| 5 |
-
branches:
|
| 6 |
-
- main
|
| 7 |
-
tags:
|
| 8 |
-
- 'v*'
|
| 9 |
-
workflow_dispatch:
|
| 10 |
-
|
| 11 |
-
env:
|
| 12 |
-
REGISTRY: ghcr.io
|
| 13 |
-
IMAGE_NAME: ${{ github.repository }}
|
| 14 |
-
|
| 15 |
-
jobs:
|
| 16 |
-
build:
|
| 17 |
-
runs-on: self-hosted
|
| 18 |
-
permissions:
|
| 19 |
-
contents: read
|
| 20 |
-
packages: write
|
| 21 |
-
|
| 22 |
-
steps:
|
| 23 |
-
- name: Checkout
|
| 24 |
-
uses: actions/checkout@v4
|
| 25 |
-
|
| 26 |
-
- name: Set up Docker Buildx
|
| 27 |
-
uses: docker/setup-buildx-action@v3
|
| 28 |
-
|
| 29 |
-
- name: Log in to GHCR
|
| 30 |
-
uses: docker/login-action@v3
|
| 31 |
-
with:
|
| 32 |
-
registry: ${{ env.REGISTRY }}
|
| 33 |
-
username: ${{ github.actor }}
|
| 34 |
-
password: ${{ secrets.GITHUB_TOKEN }}
|
| 35 |
-
|
| 36 |
-
- name: Extract metadata
|
| 37 |
-
id: meta
|
| 38 |
-
uses: docker/metadata-action@v5
|
| 39 |
-
with:
|
| 40 |
-
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
| 41 |
-
tags: |
|
| 42 |
-
type=ref,event=branch
|
| 43 |
-
type=ref,event=pr
|
| 44 |
-
type=semver,pattern={{version}}
|
| 45 |
-
type=semver,pattern={{major}}.{{minor}}
|
| 46 |
-
type=sha,prefix=
|
| 47 |
-
|
| 48 |
-
- name: Build and push
|
| 49 |
-
uses: docker/build-push-action@v5
|
| 50 |
-
with:
|
| 51 |
-
context: .
|
| 52 |
-
push: true
|
| 53 |
-
tags: ${{ steps.meta.outputs.tags }}
|
| 54 |
-
labels: ${{ steps.meta.outputs.labels }}
|
| 55 |
-
platforms: linux/amd64
|
| 56 |
-
cache-from: type=gha
|
| 57 |
-
cache-to: type=gha,mode=max
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
DELETED
|
@@ -1,21 +0,0 @@
|
|
| 1 |
-
checkpoints/**/*.pth
|
| 2 |
-
checkpoints/**/*.pt
|
| 3 |
-
checkpoints/**/*.pkl
|
| 4 |
-
checkpoints/**/*.zip
|
| 5 |
-
checkpoints/**/*.safetensors
|
| 6 |
-
checkpoints/**/*
|
| 7 |
-
|
| 8 |
-
# Ignore local dependencies (installed via pip/uv)
|
| 9 |
-
# latentsync/ - Keep for HuggingFace Spaces
|
| 10 |
-
# tigersound/
|
| 11 |
-
# FastAudioSR/
|
| 12 |
-
# descript-audiotools/
|
| 13 |
-
# models/
|
| 14 |
-
|
| 15 |
-
# Python cache and virtual environment
|
| 16 |
-
__pycache__/
|
| 17 |
-
*.pyc
|
| 18 |
-
.pytest_cache/
|
| 19 |
-
.coverage
|
| 20 |
-
.venv/
|
| 21 |
-
uv.lock
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.opencode/skills/uv-package-manager
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
../../.agents/skills/uv-package-manager
|
|
|
|
|
|
.python-version
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
3.10
|
|
|
|
|
|
AGENTS.md
DELETED
|
@@ -1,226 +0,0 @@
|
|
| 1 |
-
# AGENTS.md
|
| 2 |
-
|
| 3 |
-
This file provides guidance for agentic coding assistants working in this repository.
|
| 4 |
-
|
| 5 |
-
## Build/Install Commands
|
| 6 |
-
|
| 7 |
-
### HuggingFace Spaces (Deployment)
|
| 8 |
-
```bash
|
| 9 |
-
# HuggingFace automatically runs:
|
| 10 |
-
pip install -r requirements.txt
|
| 11 |
-
python app.py
|
| 12 |
-
```
|
| 13 |
-
|
| 14 |
-
### Local Development (with uv)
|
| 15 |
-
```bash
|
| 16 |
-
# Setup (one-time)
|
| 17 |
-
./setup_local.sh
|
| 18 |
-
|
| 19 |
-
# Or manually:
|
| 20 |
-
uv venv
|
| 21 |
-
uv pip install -r requirements.txt
|
| 22 |
-
|
| 23 |
-
# Run app
|
| 24 |
-
uv run python app.py
|
| 25 |
-
```
|
| 26 |
-
|
| 27 |
-
### Local Development (with pip - standard)
|
| 28 |
-
```bash
|
| 29 |
-
python3 -m venv .venv
|
| 30 |
-
source .venv/bin/activate
|
| 31 |
-
pip install -r requirements.txt
|
| 32 |
-
python app.py
|
| 33 |
-
```
|
| 34 |
-
|
| 35 |
-
**Note**: Use uv for faster dependency installation (10-100x faster than pip). This project currently has NO linting, formatting, or test infrastructure configured. When making changes, ensure the application runs successfully with `uv run python app.py` (or `python app.py` after activating venv).
|
| 36 |
-
|
| 37 |
-
---
|
| 38 |
-
|
| 39 |
-
## Application Architecture
|
| 40 |
-
|
| 41 |
-
This project provides lipsync functionality for English videos:
|
| 42 |
-
|
| 43 |
-
- **Lipsync Only** (`lipsync_only_video`): Apply lipsync to English video
|
| 44 |
-
|
| 45 |
-
### Core Workflow:
|
| 46 |
-
|
| 47 |
-
```
|
| 48 |
-
Upload Video β Crop Duration β Extract Audio (vocal/bg/effect) β Upsample Audio β
|
| 49 |
-
Lipsync β Merge Video + Audio
|
| 50 |
-
```
|
| 51 |
-
|
| 52 |
-
---
|
| 53 |
-
|
| 54 |
-
## Code Style Guidelines
|
| 55 |
-
|
| 56 |
-
### Imports
|
| 57 |
-
|
| 58 |
-
- Organize imports in this order:
|
| 59 |
-
1. Standard library (os, sys, pathlib, subprocess, etc.)
|
| 60 |
-
2. Third-party packages (torch, gradio, numpy, etc.)
|
| 61 |
-
3. Local/custom modules (lipsync, time_util)
|
| 62 |
-
- Use absolute imports for clarity
|
| 63 |
-
- Keep all imports at the top of files (avoid scattered imports)
|
| 64 |
-
- Remove duplicate imports
|
| 65 |
-
|
| 66 |
-
**Example:**
|
| 67 |
-
```python
|
| 68 |
-
import os
|
| 69 |
-
import subprocess
|
| 70 |
-
from pathlib import Path
|
| 71 |
-
from typing import List, Dict
|
| 72 |
-
|
| 73 |
-
import torch
|
| 74 |
-
import gradio as gr
|
| 75 |
-
from pydub import AudioSegment
|
| 76 |
-
|
| 77 |
-
from lipsync import apply_lipsync
|
| 78 |
-
from time_util import timer
|
| 79 |
-
```
|
| 80 |
-
|
| 81 |
-
---
|
| 82 |
-
|
| 83 |
-
### Formatting & Spacing
|
| 84 |
-
|
| 85 |
-
- Use 4 spaces for indentation (not tabs)
|
| 86 |
-
- Use f-strings for string formatting
|
| 87 |
-
- Keep lines under 100 characters where practical
|
| 88 |
-
- Add 2 blank lines before top-level function definitions
|
| 89 |
-
|
| 90 |
-
---
|
| 91 |
-
|
| 92 |
-
### Type Hints
|
| 93 |
-
|
| 94 |
-
- Use type hints for function signatures when clear
|
| 95 |
-
- Use `| None` for optional types (Python 3.10+) instead of `Optional[T]`
|
| 96 |
-
|
| 97 |
-
**Example:**
|
| 98 |
-
```python
|
| 99 |
-
def format_timestamp(ts: float) -> str:
|
| 100 |
-
"""Convert seconds to SRT timestamp format."""
|
| 101 |
-
hrs = int(ts // 3600)
|
| 102 |
-
# ...
|
| 103 |
-
```
|
| 104 |
-
|
| 105 |
-
---
|
| 106 |
-
|
| 107 |
-
### Naming Conventions
|
| 108 |
-
|
| 109 |
-
- **Functions & variables**: `snake_case`
|
| 110 |
-
- **Constants**: `UPPER_CASE`
|
| 111 |
-
- **Classes**: `PascalCase`
|
| 112 |
-
|
| 113 |
-
**Example:**
|
| 114 |
-
```python
|
| 115 |
-
MODEL_SIZE = "medium"
|
| 116 |
-
MAX_BATCH_MS = 300_000
|
| 117 |
-
|
| 118 |
-
def extract_audio_to_wav(input_video: str, output_dir: str):
|
| 119 |
-
pass
|
| 120 |
-
```
|
| 121 |
-
|
| 122 |
-
---
|
| 123 |
-
|
| 124 |
-
### Error Handling
|
| 125 |
-
|
| 126 |
-
- Use `subprocess.check_call()` or `subprocess.run(check=True)` for shell commands
|
| 127 |
-
- Use `gr.Error()` for user-facing errors in Gradio callbacks
|
| 128 |
-
- Include descriptive error messages
|
| 129 |
-
|
| 130 |
-
**Example:**
|
| 131 |
-
```python
|
| 132 |
-
def lipsync_only_video(video_file, duration, session_id=None, progress=None):
|
| 133 |
-
if video_file is None:
|
| 134 |
-
raise gr.Error("Please upload a clip.")
|
| 135 |
-
```
|
| 136 |
-
|
| 137 |
-
---
|
| 138 |
-
|
| 139 |
-
### Docstrings
|
| 140 |
-
|
| 141 |
-
- Add brief docstrings for non-trivial functions explaining purpose and parameters
|
| 142 |
-
- Keep docstrings concise
|
| 143 |
-
|
| 144 |
-
---
|
| 145 |
-
|
| 146 |
-
### Context Managers
|
| 147 |
-
|
| 148 |
-
- Use `@contextmanager` decorators for resource management when appropriate
|
| 149 |
-
- Use `with` statements for file operations and subprocess calls
|
| 150 |
-
|
| 151 |
-
**Example:**
|
| 152 |
-
```python
|
| 153 |
-
from contextlib import contextmanager
|
| 154 |
-
|
| 155 |
-
@contextmanager
|
| 156 |
-
def timer(name: str):
|
| 157 |
-
start = time.time()
|
| 158 |
-
print(f"{name}...")
|
| 159 |
-
yield
|
| 160 |
-
print(f" -> {name} completed in {time.time() - start:.2f} sec")
|
| 161 |
-
```
|
| 162 |
-
|
| 163 |
-
---
|
| 164 |
-
|
| 165 |
-
### Logging
|
| 166 |
-
|
| 167 |
-
- Use the `logging` module over print() for production code
|
| 168 |
-
- Configure log levels appropriately
|
| 169 |
-
|
| 170 |
-
**Example:**
|
| 171 |
-
```python
|
| 172 |
-
import logging
|
| 173 |
-
|
| 174 |
-
logging.getLogger("httpx").setLevel(logging.WARNING)
|
| 175 |
-
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
| 176 |
-
```
|
| 177 |
-
|
| 178 |
-
---
|
| 179 |
-
|
| 180 |
-
### File Paths
|
| 181 |
-
|
| 182 |
-
- Use `pathlib.Path` for cross-platform path handling when possible
|
| 183 |
-
- Use `os.path.join()` for constructing paths
|
| 184 |
-
- Use `os.makedirs(path, exist_ok=True)` for directory creation
|
| 185 |
-
|
| 186 |
-
---
|
| 187 |
-
|
| 188 |
-
### PyTorch Best Practices
|
| 189 |
-
|
| 190 |
-
- Use `torch.no_grad()` context manager for inference
|
| 191 |
-
- Move tensors to appropriate device explicitly: `.to("cuda")`
|
| 192 |
-
- Clear GPU cache after operations: `torch.cuda.empty_cache()`
|
| 193 |
-
- Set seed for reproducibility when needed: `torch.manual_seed(1234)`
|
| 194 |
-
|
| 195 |
-
---
|
| 196 |
-
|
| 197 |
-
### Gradio Integration
|
| 198 |
-
|
| 199 |
-
- Use `gr.Progress(track_tqdm=True)` for progress tracking
|
| 200 |
-
- Return appropriate outputs: tuples matching the defined outputs
|
| 201 |
-
- Handle session state carefully for multi-user environments
|
| 202 |
-
|
| 203 |
-
---
|
| 204 |
-
|
| 205 |
-
## Key Helper Functions
|
| 206 |
-
|
| 207 |
-
The application uses modular helper functions for better maintainability:
|
| 208 |
-
|
| 209 |
-
- **`setup_output_dir(session_id)`**: Creates output directory for session
|
| 210 |
-
- **`crop_video_duration(video_path, duration, output_dir)`**: Crops video using FFmpeg
|
| 211 |
-
- **`extract_audio_to_wav(video_path, output_dir)`**: Extracts and separates audio tracks
|
| 212 |
-
- **`upsample_audio(audio_path, output_dir)`**: Upsamples 16kHz audio to 48kHz
|
| 213 |
-
- **`merge_audio_video(video_path, audio_path, output_dir)`**: Merges video with audio
|
| 214 |
-
- **`apply_lipsync_to_video(video_path, audio_16k_path, output_dir)`**: Wraps lipsync.apply_lipsync
|
| 215 |
-
|
| 216 |
-
---
|
| 217 |
-
|
| 218 |
-
## Important Notes
|
| 219 |
-
|
| 220 |
-
- **CUDA Required**: This project requires GPU (CUDA) for most ML operations
|
| 221 |
-
- **Large Models**: Models are loaded at startup; avoid unnecessary reloading
|
| 222 |
-
- **Session Management**: Each user session creates a unique output directory that should be cleaned up
|
| 223 |
-
- **Clean Code**: Each function should have a single responsibility - prefer creating new helper functions over adding complexity to existing ones
|
| 224 |
-
- **Flash-attn-3**: Only available for Linux x86_64. Comment out in requirements.txt for local testing on macOS
|
| 225 |
-
- **Dependencies Managed via uv (local) / pip (HuggingFace)**: All packages are installed in `.venv/` (gitignored)
|
| 226 |
-
- **Local Dependencies Ignored**: latentsync/, tigersound/, FastAudioSR/, descript-audiotools/ directories are gitignored - packages are installed from git repos via requirements.txt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dockerfile
DELETED
|
@@ -1,48 +0,0 @@
|
|
| 1 |
-
FROM docker.io/nvidia/cuda:12.3.2-cudnn9-devel-ubuntu22.04 AS builder
|
| 2 |
-
|
| 3 |
-
ENV DEBIAN_FRONTEND=noninteractive
|
| 4 |
-
|
| 5 |
-
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 6 |
-
git rsync \
|
| 7 |
-
make build-essential libssl-dev zlib1g-dev \
|
| 8 |
-
libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
|
| 9 |
-
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev git-lfs \
|
| 10 |
-
ffmpeg libsm6 libxext6 cmake libgl1 \
|
| 11 |
-
&& rm -rf /var/lib/apt/lists/* \
|
| 12 |
-
&& git lfs install
|
| 13 |
-
|
| 14 |
-
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 15 |
-
ENV PATH="/root/.local/bin:$PATH"
|
| 16 |
-
|
| 17 |
-
RUN uv venv --python 3.10 /opt/venv
|
| 18 |
-
|
| 19 |
-
WORKDIR /app
|
| 20 |
-
COPY requirements.txt .
|
| 21 |
-
RUN . /opt/venv/bin/activate && uv pip install -r requirements.txt
|
| 22 |
-
|
| 23 |
-
FROM docker.io/nvidia/cuda:12.3.2-cudnn9-runtime-ubuntu22.04
|
| 24 |
-
|
| 25 |
-
ENV DEBIAN_FRONTEND=noninteractive
|
| 26 |
-
|
| 27 |
-
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 28 |
-
ffmpeg libsm6 libxext6 libgl1 \
|
| 29 |
-
git-lfs \
|
| 30 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 31 |
-
|
| 32 |
-
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 33 |
-
ENV PATH="/root/.local/bin:$PATH"
|
| 34 |
-
|
| 35 |
-
WORKDIR /app
|
| 36 |
-
|
| 37 |
-
COPY --from=builder /opt/venv /opt/venv
|
| 38 |
-
COPY . .
|
| 39 |
-
|
| 40 |
-
RUN mkdir -p /app/processed_results
|
| 41 |
-
RUN mkdir -p /root/.cache/torch/hub/checkpoints
|
| 42 |
-
|
| 43 |
-
ENV PYTHONUNBUFFERED=1
|
| 44 |
-
ENV PROCESSED_RESULTS=/app/processed_results
|
| 45 |
-
|
| 46 |
-
EXPOSE 7860
|
| 47 |
-
|
| 48 |
-
CMD ["uv", "run", "app.py"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ORIGINAL_README.md
DELETED
|
@@ -1,106 +0,0 @@
|
|
| 1 |
-
LatentSync: Audio Conditioned Latent Diffusion Models for Lip Sync
|
| 2 |
-
|
| 3 |
-
## π Abstract
|
| 4 |
-
We present LatentSync, an end-to-end lip sync framework based on audio conditioned latent diffusion models without any intermediate motion representation, diverging from previous diffusion-based lip sync methods based on pixel space diffusion or two-stage generation. Our framework can leverage powerful capabilities of Stable Diffusion to directly model complex audio-visual correlations. Additionally, we found that diffusion-based lip sync methods exhibit inferior temporal consistency due to inconsistency in diffusion process across different frames. We propose Temporal REPresentation Alignment (TREPA) to enhance temporal consistency while preserving lip-sync accuracy. TREPA uses temporal representations extracted by large-scale self-supervised video models to align generated frames with ground truth frames.
|
| 5 |
-
|
| 6 |
-
## ποΈ Framework
|
| 7 |
-
LatentSync uses Whisper to convert melspectrogram into audio embeddings, which are then integrated into U-Net via cross-attention layers. The reference and masked frames are channel-wise concatenated with noised latents as input of U-Net. In training process, we use one-step method to get estimated clean latents from predicted noises, which are then decoded to obtain the estimated clean frames. The TREPA, LPIPS and SyncNet loss are added in the pixel space.
|
| 8 |
-
|
| 9 |
-
## π¬ Demo
|
| 10 |
-
|
| 11 |
-
| | |
|
| 12 |
-
| --- | --- |
|
| 13 |
-
| __Original video__ | __Lip-synced video__ |
|
| 14 |
-
| demo2_input.mp4 | demo2_output_v1.6.mp4 |
|
| 15 |
-
| demo3_input.mp4 | demo3_output_v1.6.mp4 |
|
| 16 |
-
| demo4_input.mp4 | demo4_output_v1.6.mp4 |
|
| 17 |
-
| demo5_input.mp4 | demo5_output_v1.6.mp4 |
|
| 18 |
-
| demo4_video.mp4 | demo4_output.mp4 |
|
| 19 |
-
|
| 20 |
-
(Photorealistic videos are filmed by contracted models, and anime videos are from VASA-1 and EMO)
|
| 21 |
-
|
| 22 |
-
## π Open-source Plan
|
| 23 |
-
|
| 24 |
-
- Inference code and checkpoints
|
| 25 |
-
- Data processing pipeline
|
| 26 |
-
- Training code
|
| 27 |
-
|
| 28 |
-
## π§ Setting up the Environment
|
| 29 |
-
Install the required packages and download the checkpoints via:
|
| 30 |
-
|
| 31 |
-
```bash
|
| 32 |
-
source setup_env.sh
|
| 33 |
-
```
|
| 34 |
-
|
| 35 |
-
If the download is successful, the checkpoints should appear as follows:
|
| 36 |
-
|
| 37 |
-
```
|
| 38 |
-
./checkpoints/
|
| 39 |
-
|-- latentsync_unet.pt
|
| 40 |
-
|-- latentsync_syncnet.pt
|
| 41 |
-
|-- whisper
|
| 42 |
-
| `-- tiny.pt
|
| 43 |
-
|-- auxiliary
|
| 44 |
-
| |-- 2DFAN4-cd938726ad.zip
|
| 45 |
-
| |-- i3d_torchscript.pt
|
| 46 |
-
| |-- koniq_pretrained.pkl
|
| 47 |
-
| |-- s3fd-619a316812.pth
|
| 48 |
-
| |-- sfd_face.pth
|
| 49 |
-
| |-- syncnet_v2.model
|
| 50 |
-
| |-- vgg16-397923af.pth
|
| 51 |
-
| `-- vit_g_hybrid_pt_1200e_ssv2_ft.pth
|
| 52 |
-
```
|
| 53 |
-
|
| 54 |
-
These already include all the checkpoints required for latentsync training and inference. If you just want to try inference, you only need to download `latentsync_unet.pt` and `tiny.pt` from our HuggingFace repo
|
| 55 |
-
|
| 56 |
-
## π Inference
|
| 57 |
-
Run the script for inference, which requires about 6.5 GB GPU memory.
|
| 58 |
-
|
| 59 |
-
```bash
|
| 60 |
-
./inference.sh
|
| 61 |
-
```
|
| 62 |
-
|
| 63 |
-
You can try adjusting the following inference parameters to achieve better results:
|
| 64 |
-
|
| 65 |
-
- `inference_steps` [20-50]: A higher value improves visual quality but slows down the generation speed.
|
| 66 |
-
- `guidance_scale` [1.0-3.0]: A higher value improves lip-sync accuracy but may cause the video distortion or jitter.
|
| 67 |
-
|
| 68 |
-
## π Data Processing Pipeline
|
| 69 |
-
The complete data processing pipeline includes the following steps:
|
| 70 |
-
|
| 71 |
-
1. Remove the broken video files.
|
| 72 |
-
2. Resample the video FPS to 25, and resample the audio to 16000 Hz.
|
| 73 |
-
3. Scene detect via PySceneDetect.
|
| 74 |
-
4. Split each video into 5-10 second segments.
|
| 75 |
-
5. Remove videos where the face is smaller than 256 $\times$ 256, as well as videos with more than one face.
|
| 76 |
-
6. Affine transform the faces according to the landmarks detected by face-alignment, then resize to 256 $\times$ 256.
|
| 77 |
-
7. Remove videos with sync confidence score lower than 3, and adjust the audio-visual offset to 0.
|
| 78 |
-
8. Calculate hyperIQA score, and remove videos with scores lower than 40.
|
| 79 |
-
|
| 80 |
-
Run the script to execute the data processing pipeline:
|
| 81 |
-
|
| 82 |
-
```bash
|
| 83 |
-
./data_processing_pipeline.sh
|
| 84 |
-
```
|
| 85 |
-
|
| 86 |
-
You should change the parameter `input_dir` in the script to specify the data directory to be processed. The processed data will be saved in the same directory. Each step will generate a new directory to prevent the need to redo the entire pipeline in case the process is interrupted by an unexpected error.
|
| 87 |
-
|
| 88 |
-
## ποΈββοΈ Training U-Net
|
| 89 |
-
Before training, you must process the data as described above and download all the checkpoints. We released a pretrained SyncNet with 94% accuracy on VoxCeleb2 dataset for the supervision of U-Net training. Note that this SyncNet is trained on affine transformed videos, so when using or evaluating this SyncNet, you need to perform affine transformation on the video first (the code of affine transformation is included in the data processing pipeline).
|
| 90 |
-
|
| 91 |
-
If all the preparations are complete, you can train the U-Net with the following script:
|
| 92 |
-
|
| 93 |
-
```bash
|
| 94 |
-
./train_unet.sh
|
| 95 |
-
```
|
| 96 |
-
|
| 97 |
-
You should change the parameters in the U-Net config file to specify the data directory, checkpoint save path, and other training hyperparameters.
|
| 98 |
-
|
| 99 |
-
## ποΈββοΈ Training SyncNet
|
| 100 |
-
In case you want to train SyncNet on your own datasets, you can run the following script. The data processing pipeline for SyncNet is the same as for U-Net.
|
| 101 |
-
|
| 102 |
-
```bash
|
| 103 |
-
./train_syncnet.sh
|
| 104 |
-
```
|
| 105 |
-
|
| 106 |
-
After `validations_steps` training, the loss charts will be saved in `train_output_dir`. They contain both the training and validation loss.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,161 +1,10 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: pink
|
| 6 |
-
sdk:
|
| 7 |
-
sdk_version: 5.24.0
|
| 8 |
-
python_version: "3.10"
|
| 9 |
-
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
-
short_description: Lipsync video (English only) - LatentSync 1.6
|
| 12 |
---
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
Lipsync video with custom audio (English only) using **LatentSync 1.6** from ByteDance.
|
| 17 |
-
|
| 18 |
-
## Features
|
| 19 |
-
|
| 20 |
-
- **Resolution**: 512x512 (LatentSync 1.6)
|
| 21 |
-
- **Auto-download**: Checkpoints from `ByteDance/LatentSync-1.6`
|
| 22 |
-
- **Face detection**: Automatic face detection and cropping
|
| 23 |
-
- **Audio processing**: Audio separation, upsampling
|
| 24 |
-
- **Multiple outputs**: Step-by-step processing visualization
|
| 25 |
-
|
| 26 |
-
## HuggingFace Spaces Deployment
|
| 27 |
-
|
| 28 |
-
### 1. TαΊ‘o Space mα»i trΓͺn HuggingFace
|
| 29 |
-
|
| 30 |
-
- VΓ o <https://huggingface.co/new-space>
|
| 31 |
-
- Chα»n:
|
| 32 |
-
- **Owner**: Username cα»§a bαΊ‘n
|
| 33 |
-
- **Space name**: TΓͺn bαΊ‘n muα»n (vΓ dα»₯: `lipsync-demo`)
|
| 34 |
-
- **SDK**: Gradio
|
| 35 |
-
- **Hardware**: GPU (cαΊ§n Γt nhαΊ₯t 18GB VRAM cho LatentSync 1.6)
|
| 36 |
-
- **Visibility**: Public hoαΊ·c Private
|
| 37 |
-
|
| 38 |
-
### 2. ΔαΊ©y code lΓͺn Space
|
| 39 |
-
|
| 40 |
-
**CΓ‘ch 1: DΓΉng Git**
|
| 41 |
-
|
| 42 |
-
```bash
|
| 43 |
-
git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
|
| 44 |
-
cd YOUR_SPACE_NAME
|
| 45 |
-
git remote add origin https://github.com/naicoi/OutofLipSync
|
| 46 |
-
git pull origin main
|
| 47 |
-
git push origin main
|
| 48 |
-
```
|
| 49 |
-
|
| 50 |
-
**CΓ‘ch 2: DΓΉng HuggingFace CLI**
|
| 51 |
-
|
| 52 |
-
```bash
|
| 53 |
-
# Install huggingface-cli nαΊΏu chΖ°a cΓ³
|
| 54 |
-
pip install huggingface_hub
|
| 55 |
-
|
| 56 |
-
# Login
|
| 57 |
-
huggingface-cli login
|
| 58 |
-
|
| 59 |
-
# Push code lΓͺn Space
|
| 60 |
-
git push https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME main
|
| 61 |
-
```
|
| 62 |
-
|
| 63 |
-
### 3. Δợi build vΓ deploy
|
| 64 |
-
|
| 65 |
-
- HuggingFace sαΊ½ tα»± Δα»ng build vΓ deploy
|
| 66 |
-
- Check status α» tab "Settings" β "Build"
|
| 67 |
-
- Khi build xong, app sαΊ½ chαΊ‘y tαΊ‘i: `https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME`
|
| 68 |
-
|
| 69 |
-
### 4. YΓͺu cαΊ§u
|
| 70 |
-
|
| 71 |
-
- **GPU**: Space cαΊ§n cΓ³ GPU (tα»i thiα»u 18GB VRAM cho LatentSync 1.6)
|
| 72 |
-
- **Runtime**: Python 3.10
|
| 73 |
-
- **Disk space**: ~5GB cho checkpoints
|
| 74 |
-
|
| 75 |
-
### 5. LΖ°u Γ½
|
| 76 |
-
|
| 77 |
-
- Checkpoint Δược tαΊ£i tα»± Δα»ng tα»« `ByteDance/LatentSync-1.6` khi khα»i Δα»ng
|
| 78 |
-
- QuΓ‘ trΓ¬nh tαΊ£i checkpoint cΓ³ thα» mαΊ₯t vΓ i phΓΊt
|
| 79 |
-
- Audio target chỠhỠtrợ tiếng Anh
|
| 80 |
-
|
| 81 |
-
---
|
| 82 |
-
|
| 83 |
-
## π Deployment
|
| 84 |
-
|
| 85 |
-
### HuggingFace Spaces
|
| 86 |
-
|
| 87 |
-
1. Create a Space on HuggingFace
|
| 88 |
-
2. Push this repository to your Space
|
| 89 |
-
3. Done! HuggingFace will automatically:
|
| 90 |
-
- Create Python environment
|
| 91 |
-
- Install dependencies from requirements.txt
|
| 92 |
-
- Start the application
|
| 93 |
-
|
| 94 |
-
**Requirements:**
|
| 95 |
-
|
| 96 |
-
- Hardware: A10G GPU (recommended, 24GB VRAM)
|
| 97 |
-
- Python: 3.10
|
| 98 |
-
|
| 99 |
-
## π» Local Development
|
| 100 |
-
|
| 101 |
-
### Option 1: Using uv (Fast - Recommended)
|
| 102 |
-
|
| 103 |
-
```bash
|
| 104 |
-
# Install uv (macOS/Linux)
|
| 105 |
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 106 |
-
# Or: brew install uv
|
| 107 |
-
|
| 108 |
-
# Setup and install
|
| 109 |
-
./setup_local.sh
|
| 110 |
-
|
| 111 |
-
# Run application
|
| 112 |
-
uv run python app.py
|
| 113 |
-
```
|
| 114 |
-
|
| 115 |
-
**Why uv?** 10-100x faster than pip for dependency management!
|
| 116 |
-
|
| 117 |
-
### Option 2: Using pip (Standard)
|
| 118 |
-
|
| 119 |
-
```bash
|
| 120 |
-
# Create venv
|
| 121 |
-
python3 -m venv .venv
|
| 122 |
-
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
| 123 |
-
|
| 124 |
-
# Install dependencies
|
| 125 |
-
pip install -r requirements.txt
|
| 126 |
-
|
| 127 |
-
# Run application
|
| 128 |
-
python app.py
|
| 129 |
-
```
|
| 130 |
-
|
| 131 |
-
## π¦ Dependencies
|
| 132 |
-
|
| 133 |
-
- **requirements.txt**: All dependencies for application
|
| 134 |
-
- Packages are installed in `.venv/` (ignored by git)
|
| 135 |
-
- Git dependencies: LatentSync, FastAudioSR, tigersound, descript-audiotools
|
| 136 |
-
|
| 137 |
-
## β οΈ Important Notes
|
| 138 |
-
|
| 139 |
-
### Flash-attn-3 for Local Testing
|
| 140 |
-
|
| 141 |
-
The `flash-attn-3` package only provides wheels for Linux x86_64:
|
| 142 |
-
|
| 143 |
-
- **HuggingFace (Linux)**: β
Works automatically
|
| 144 |
-
- **Local (macOS)**: β Will fail during installation
|
| 145 |
-
|
| 146 |
-
**Workaround for local testing:**
|
| 147 |
-
|
| 148 |
-
```bash
|
| 149 |
-
# Comment out flash-attn-3 in requirements.txt for local testing
|
| 150 |
-
# Uncomment before pushing to HuggingFace
|
| 151 |
-
```
|
| 152 |
-
|
| 153 |
-
### Checkpoints
|
| 154 |
-
|
| 155 |
-
Checkpoints are automatically downloaded from `ByteDance/LatentSync-1.6` on startup.
|
| 156 |
-
|
| 157 |
-
### Audio Language
|
| 158 |
-
|
| 159 |
-
Target audio supports **English only**.
|
| 160 |
-
|
| 161 |
-
Check out the configuration reference at <https://huggingface.co/docs/hub/spaces-config-reference>
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Lipsync Docker
|
| 3 |
+
emoji: π
|
| 4 |
+
colorFrom: yellow
|
| 5 |
colorTo: pink
|
| 6 |
+
sdk: docker
|
|
|
|
|
|
|
|
|
|
| 7 |
pinned: false
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
DELETED
|
@@ -1,189 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
OutofLipSync - Lipsync Only Application
|
| 3 |
-
Main Gradio UI module
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import os
|
| 7 |
-
|
| 8 |
-
# Optimize PyTorch memory allocation to reduce fragmentation
|
| 9 |
-
os.environ["PYTORCH_ALLOC_CONF"] = "expandable_segments:True"
|
| 10 |
-
|
| 11 |
-
import logging
|
| 12 |
-
import sys
|
| 13 |
-
import shutil
|
| 14 |
-
|
| 15 |
-
import gradio as gr
|
| 16 |
-
import torchvision.transforms.functional as _F
|
| 17 |
-
from processing import lipsync_with_audio_target
|
| 18 |
-
from shared.model_manager import ModelManager
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
logging.info("=" * 60)
|
| 22 |
-
logging.info("APPLICATION STARTING")
|
| 23 |
-
logging.info(f"Python version: {sys.version}")
|
| 24 |
-
logging.info(f"Platform: {sys.platform}")
|
| 25 |
-
logging.info(f"Working directory: {os.getcwd()}")
|
| 26 |
-
logging.info("=" * 60)
|
| 27 |
-
|
| 28 |
-
sys.modules["torchvision.transforms.functional_tensor"] = _F
|
| 29 |
-
|
| 30 |
-
os.environ["PROCESSED_RESULTS"] = os.path.join(os.getcwd(), "processed_results")
|
| 31 |
-
os.makedirs(os.environ["PROCESSED_RESULTS"], exist_ok=True)
|
| 32 |
-
|
| 33 |
-
src = "/models"
|
| 34 |
-
dst = os.path.expanduser("~/.cache/torch/hub/checkpoints")
|
| 35 |
-
|
| 36 |
-
os.makedirs(dst, exist_ok=True)
|
| 37 |
-
|
| 38 |
-
if os.path.exists(src):
|
| 39 |
-
for item in os.listdir(src):
|
| 40 |
-
src_path = os.path.join(src, item)
|
| 41 |
-
dst_path = os.path.join(dst, item)
|
| 42 |
-
if os.path.isfile(src_path) and not os.path.exists(dst_path):
|
| 43 |
-
shutil.copy2(src_path, dst_path)
|
| 44 |
-
print(f"Copied {item} to {dst}")
|
| 45 |
-
|
| 46 |
-
print("Done copying checkpoints!")
|
| 47 |
-
|
| 48 |
-
print("Loading LatentSync models...")
|
| 49 |
-
manager = ModelManager.get_instance()
|
| 50 |
-
manager.preload_latentsync_models()
|
| 51 |
-
print("Models loaded!")
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
css = """
|
| 55 |
-
#col-container {
|
| 56 |
-
margin: 0 auto;
|
| 57 |
-
max-width: 1400px;
|
| 58 |
-
padding: 2rem 1rem;
|
| 59 |
-
}
|
| 60 |
-
.header-container {
|
| 61 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 62 |
-
border-radius: 1rem;
|
| 63 |
-
padding: 2rem;
|
| 64 |
-
margin-bottom: 1.5rem;
|
| 65 |
-
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);
|
| 66 |
-
}
|
| 67 |
-
.header-title {
|
| 68 |
-
color: white;
|
| 69 |
-
margin: 0;
|
| 70 |
-
font-size: 2.5rem;
|
| 71 |
-
font-weight: 700;
|
| 72 |
-
letter-spacing: -0.5px;
|
| 73 |
-
}
|
| 74 |
-
.header-subtitle {
|
| 75 |
-
color: rgba(255, 255, 255, 0.9);
|
| 76 |
-
margin: 0.5rem 0 0;
|
| 77 |
-
font-size: 1.1rem;
|
| 78 |
-
}
|
| 79 |
-
.card-section {
|
| 80 |
-
background: white;
|
| 81 |
-
border-radius: 1rem;
|
| 82 |
-
padding: 1.5rem;
|
| 83 |
-
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
| 84 |
-
border: 1px solid #e5e7eb;
|
| 85 |
-
height: 100%;
|
| 86 |
-
transition: all 0.3s ease;
|
| 87 |
-
}
|
| 88 |
-
.card-section:hover {
|
| 89 |
-
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
|
| 90 |
-
}
|
| 91 |
-
.section-header {
|
| 92 |
-
color: #1f2937;
|
| 93 |
-
font-size: 1.25rem;
|
| 94 |
-
font-weight: 600;
|
| 95 |
-
margin-bottom: 1rem;
|
| 96 |
-
display: flex;
|
| 97 |
-
align-items: center;
|
| 98 |
-
gap: 0.5rem;
|
| 99 |
-
}
|
| 100 |
-
.footer-container {
|
| 101 |
-
margin-top: 2rem;
|
| 102 |
-
padding-top: 1.5rem;
|
| 103 |
-
border-top: 1px solid #e5e7eb;
|
| 104 |
-
text-align: center;
|
| 105 |
-
color: #6b7280;
|
| 106 |
-
font-size: 0.9rem;
|
| 107 |
-
}
|
| 108 |
-
.footer-link {
|
| 109 |
-
color: #667eea;
|
| 110 |
-
text-decoration: none;
|
| 111 |
-
transition: color 0.2s ease;
|
| 112 |
-
}
|
| 113 |
-
.footer-link:hover {
|
| 114 |
-
color: #764ba2;
|
| 115 |
-
}
|
| 116 |
-
"""
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
def cleanup(request: gr.Request):
|
| 120 |
-
sid = request.session_hash
|
| 121 |
-
if sid:
|
| 122 |
-
print(f"{sid} left")
|
| 123 |
-
d1 = os.path.join(os.environ["PROCESSED_RESULTS"], sid)
|
| 124 |
-
shutil.rmtree(d1, ignore_errors=True)
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
def start_session(request: gr.Request):
|
| 128 |
-
return request.session_hash
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
with gr.Blocks(css=css) as demo:
|
| 132 |
-
session_state = gr.State()
|
| 133 |
-
demo.load(fn=start_session, outputs=[session_state])
|
| 134 |
-
|
| 135 |
-
with gr.Column(elem_id="col-container"):
|
| 136 |
-
gr.HTML(
|
| 137 |
-
"""
|
| 138 |
-
<div class="header-container">
|
| 139 |
-
<h1 class="header-title">π¬ OutofLipSync</h1>
|
| 140 |
-
<p class="header-subtitle">Lipsync video with custom audio (English only)</p>
|
| 141 |
-
</div>
|
| 142 |
-
"""
|
| 143 |
-
)
|
| 144 |
-
|
| 145 |
-
with gr.Row():
|
| 146 |
-
with gr.Column(scale=1):
|
| 147 |
-
with gr.Group(elem_classes="card-section"):
|
| 148 |
-
gr.HTML('<div class="section-header">πΉ Upload Video</div>')
|
| 149 |
-
video_input = gr.Video(label="Video Source", height=400)
|
| 150 |
-
|
| 151 |
-
with gr.Column(scale=1):
|
| 152 |
-
with gr.Group(elem_classes="card-section"):
|
| 153 |
-
gr.HTML('<div class="section-header">π΅ Upload Audio</div>')
|
| 154 |
-
audio_input = gr.Audio(
|
| 155 |
-
label="Target Audio (English only)", type="filepath"
|
| 156 |
-
)
|
| 157 |
-
quality_level = gr.Radio(
|
| 158 |
-
choices=["Fast", "Normal", "Medium", "Best", "Super Best"],
|
| 159 |
-
value="Normal",
|
| 160 |
-
label="Quality",
|
| 161 |
-
)
|
| 162 |
-
lipsync_only_btn = gr.Button(
|
| 163 |
-
"π Lipsync", variant="primary", size="lg"
|
| 164 |
-
)
|
| 165 |
-
|
| 166 |
-
with gr.Group(elem_classes="card-section"):
|
| 167 |
-
gr.HTML('<div class="section-header">π¬ Final Output</div>')
|
| 168 |
-
final_video = gr.Video(label="Final Output", height=500)
|
| 169 |
-
|
| 170 |
-
gr.HTML(
|
| 171 |
-
"""
|
| 172 |
-
<div class="footer-container">
|
| 173 |
-
<p>Made with β₯ by <a href="#" class="footer-link">LT Tech</a> β’ Powered by <a href="#" class="footer-link">LatentSync</a></p>
|
| 174 |
-
<p style="margin-top: 0.5rem; font-size: 0.85rem;">Version 1.0.0</p>
|
| 175 |
-
</div>
|
| 176 |
-
"""
|
| 177 |
-
)
|
| 178 |
-
|
| 179 |
-
lipsync_only_btn.click(
|
| 180 |
-
fn=lipsync_with_audio_target,
|
| 181 |
-
inputs=[video_input, audio_input, session_state, quality_level],
|
| 182 |
-
outputs=[final_video],
|
| 183 |
-
)
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
if __name__ == "__main__":
|
| 187 |
-
demo.unload(cleanup)
|
| 188 |
-
demo.queue()
|
| 189 |
-
demo.launch(ssr_mode=False, share=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assets/readme.md
DELETED
|
File without changes
|
audio_processing.py
DELETED
|
@@ -1,211 +0,0 @@
|
|
| 1 |
-
"""Audio processing utilities for OutofLipSync"""
|
| 2 |
-
|
| 3 |
-
import os
|
| 4 |
-
import subprocess
|
| 5 |
-
from ffmpy import FFmpeg, FFRuntimeError
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
def get_audio_duration(audio_path: str, max_duration: float = 30.0) -> float:
|
| 9 |
-
"""Get audio file duration, raise error if exceeds max_duration
|
| 10 |
-
|
| 11 |
-
Args:
|
| 12 |
-
audio_path: Path to audio file
|
| 13 |
-
max_duration: Maximum duration in seconds (default 30)
|
| 14 |
-
|
| 15 |
-
Returns:
|
| 16 |
-
Duration in seconds
|
| 17 |
-
|
| 18 |
-
Raises:
|
| 19 |
-
ValueError: If audio duration exceeds max_duration
|
| 20 |
-
"""
|
| 21 |
-
cmd = [
|
| 22 |
-
"ffprobe",
|
| 23 |
-
"-v",
|
| 24 |
-
"error",
|
| 25 |
-
"-show_entries",
|
| 26 |
-
"format=duration",
|
| 27 |
-
"-of",
|
| 28 |
-
"default=noprint_wrappers=1:nokey=1",
|
| 29 |
-
audio_path,
|
| 30 |
-
]
|
| 31 |
-
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 32 |
-
duration = float(result.stdout.strip())
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
return duration
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
# def prepare_target_audio(audio_path: str, output_dir: str) -> tuple:
|
| 40 |
-
# """Prepare target audio for lipsync (DEPRECATED - use prepare_audio_for_lipsync instead)
|
| 41 |
-
#
|
| 42 |
-
# Args:
|
| 43 |
-
# audio_path: Path to target audio
|
| 44 |
-
# output_dir: Output directory
|
| 45 |
-
#
|
| 46 |
-
# Returns:
|
| 47 |
-
# (audio_16k_path, audio_upsampled_path)
|
| 48 |
-
# """
|
| 49 |
-
# audio_16k = os.path.join(output_dir, "audio_16k.wav")
|
| 50 |
-
# audio_upsampled = os.path.join(output_dir, "audio_upsampled.wav")
|
| 51 |
-
#
|
| 52 |
-
# ffmpeg1 = FFmpeg(
|
| 53 |
-
# inputs={audio_path: None},
|
| 54 |
-
# outputs={
|
| 55 |
-
# audio_16k: [
|
| 56 |
-
# "-ar",
|
| 57 |
-
# "16000",
|
| 58 |
-
# "-ac",
|
| 59 |
-
# "1",
|
| 60 |
-
# "-acodec",
|
| 61 |
-
# "pcm_s16le",
|
| 62 |
-
# "-loglevel",
|
| 63 |
-
# "error",
|
| 64 |
-
# "-y",
|
| 65 |
-
# ]
|
| 66 |
-
# },
|
| 67 |
-
# )
|
| 68 |
-
# try:
|
| 69 |
-
# ffmpeg1.run()
|
| 70 |
-
# except FFRuntimeError as e:
|
| 71 |
-
# raise Exception(f"FFmpeg failed to convert to 16k: {e}")
|
| 72 |
-
#
|
| 73 |
-
# ffmpeg2 = FFmpeg(
|
| 74 |
-
# inputs={audio_16k: None},
|
| 75 |
-
# outputs={
|
| 76 |
-
# audio_upsampled: [
|
| 77 |
-
# "-ar",
|
| 78 |
-
# "48000",
|
| 79 |
-
# "-ac",
|
| 80 |
-
# "1",
|
| 81 |
-
# "-acodec",
|
| 82 |
-
# "pcm_s16le",
|
| 83 |
-
# "-loglevel",
|
| 84 |
-
# "error",
|
| 85 |
-
# "-y",
|
| 86 |
-
# ]
|
| 87 |
-
# },
|
| 88 |
-
# )
|
| 89 |
-
# try:
|
| 90 |
-
# ffmpeg2.run()
|
| 91 |
-
# except FFRuntimeError as e:
|
| 92 |
-
# raise Exception(f"FFmpeg failed to upsample to 48k: {e}")
|
| 93 |
-
#
|
| 94 |
-
# return audio_16k, audio_upsampled
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
def prepare_audio_for_lipsync(audio_path: str, output_dir: str) -> str:
|
| 98 |
-
"""ChuαΊ©n bα» audio 16kHz mono cho lipsync pipeline
|
| 99 |
-
|
| 100 |
-
Args:
|
| 101 |
-
audio_path: Path audio gα»c
|
| 102 |
-
output_dir: Output directory
|
| 103 |
-
|
| 104 |
-
Returns:
|
| 105 |
-
Path audio 16k WAV
|
| 106 |
-
"""
|
| 107 |
-
audio_16k = os.path.join(output_dir, "audio_16k.wav")
|
| 108 |
-
|
| 109 |
-
ffmpeg = FFmpeg(
|
| 110 |
-
inputs={audio_path: None},
|
| 111 |
-
outputs={
|
| 112 |
-
audio_16k: [
|
| 113 |
-
"-ar",
|
| 114 |
-
"16000",
|
| 115 |
-
"-ac",
|
| 116 |
-
"1",
|
| 117 |
-
"-acodec",
|
| 118 |
-
"pcm_s16le",
|
| 119 |
-
"-loglevel",
|
| 120 |
-
"error",
|
| 121 |
-
"-y",
|
| 122 |
-
]
|
| 123 |
-
},
|
| 124 |
-
)
|
| 125 |
-
try:
|
| 126 |
-
ffmpeg.run()
|
| 127 |
-
except FFRuntimeError as e:
|
| 128 |
-
raise Exception(f"FFmpeg failed to convert to 16k: {e}")
|
| 129 |
-
|
| 130 |
-
return audio_16k
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
def prepare_audio_for_youtube_aac(audio_path: str, output_dir: str) -> str:
|
| 134 |
-
"""ChuαΊ©n bα» audio theo chuαΊ©n YouTube (AAC)
|
| 135 |
-
|
| 136 |
-
Args:
|
| 137 |
-
audio_path: Path audio gα»c
|
| 138 |
-
output_dir: Output directory
|
| 139 |
-
|
| 140 |
-
Returns:
|
| 141 |
-
Path audio YouTube (AAC)
|
| 142 |
-
"""
|
| 143 |
-
from config import (
|
| 144 |
-
YOUTUBE_AUDIO_CODEC,
|
| 145 |
-
YOUTUBE_AUDIO_BITRATE,
|
| 146 |
-
YOUTUBE_AUDIO_SAMPLE_RATE,
|
| 147 |
-
)
|
| 148 |
-
|
| 149 |
-
output_path = os.path.join(output_dir, "audio_youtube.aac")
|
| 150 |
-
|
| 151 |
-
ffmpeg = FFmpeg(
|
| 152 |
-
inputs={audio_path: None},
|
| 153 |
-
outputs={
|
| 154 |
-
output_path: [
|
| 155 |
-
"-ar",
|
| 156 |
-
str(YOUTUBE_AUDIO_SAMPLE_RATE),
|
| 157 |
-
"-ac",
|
| 158 |
-
"2",
|
| 159 |
-
"-acodec",
|
| 160 |
-
YOUTUBE_AUDIO_CODEC,
|
| 161 |
-
"-b:a",
|
| 162 |
-
YOUTUBE_AUDIO_BITRATE,
|
| 163 |
-
"-loglevel",
|
| 164 |
-
"error",
|
| 165 |
-
"-y",
|
| 166 |
-
]
|
| 167 |
-
},
|
| 168 |
-
)
|
| 169 |
-
try:
|
| 170 |
-
ffmpeg.run()
|
| 171 |
-
except FFRuntimeError as e:
|
| 172 |
-
raise Exception(f"FFmpeg failed to prepare audio for YouTube: {e}")
|
| 173 |
-
|
| 174 |
-
return output_path
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
def prepare_audio_for_youtube(audio_path: str, output_dir: str) -> str:
|
| 178 |
-
"""
|
| 179 |
-
ChuαΊ©n bα» audio tα»i Ζ°u cho YouTube
|
| 180 |
-
|
| 181 |
-
Args:
|
| 182 |
-
audio_path: Path to audio file (WAV)
|
| 183 |
-
output_dir: Output directory
|
| 184 |
-
|
| 185 |
-
Returns:
|
| 186 |
-
Path to audio file (WAV 48kHz PCM)
|
| 187 |
-
"""
|
| 188 |
-
output_path = os.path.join(output_dir, "audio_final.wav")
|
| 189 |
-
|
| 190 |
-
ffmpeg = FFmpeg(
|
| 191 |
-
inputs={audio_path: None},
|
| 192 |
-
outputs={
|
| 193 |
-
output_path: [
|
| 194 |
-
"-ar",
|
| 195 |
-
"48000",
|
| 196 |
-
"-ac",
|
| 197 |
-
"2",
|
| 198 |
-
"-acodec",
|
| 199 |
-
"pcm_s16le",
|
| 200 |
-
"-loglevel",
|
| 201 |
-
"error",
|
| 202 |
-
"-y",
|
| 203 |
-
]
|
| 204 |
-
},
|
| 205 |
-
)
|
| 206 |
-
try:
|
| 207 |
-
ffmpeg.run()
|
| 208 |
-
except FFRuntimeError as e:
|
| 209 |
-
raise Exception(f"FFmpeg failed to prepare audio for YouTube: {e}")
|
| 210 |
-
|
| 211 |
-
return output_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
checkpoints/readme.md
DELETED
|
File without changes
|
config.py
DELETED
|
@@ -1,25 +0,0 @@
|
|
| 1 |
-
"""Configuration constants and global settings for OutofLipSync"""
|
| 2 |
-
|
| 3 |
-
import os
|
| 4 |
-
|
| 5 |
-
# Models directory - from environment variable or default to /models
|
| 6 |
-
MODELS_DIR = os.getenv("MODELS_DIR", "/models")
|
| 7 |
-
|
| 8 |
-
# Video settings
|
| 9 |
-
DEFAULT_DURATION = 10
|
| 10 |
-
MIN_DURATION = 5
|
| 11 |
-
MAX_DURATION = 60
|
| 12 |
-
|
| 13 |
-
# Processing directory
|
| 14 |
-
PROCESSED_RESULTS_DIR = "processed_results"
|
| 15 |
-
|
| 16 |
-
# YouTube quality settings
|
| 17 |
-
YOUTUBE_VIDEO_PRESET = "slow"
|
| 18 |
-
YOUTUBE_VIDEO_CRF = 18
|
| 19 |
-
YOUTUBE_VIDEO_PROFILE = "high"
|
| 20 |
-
YOUTUBE_VIDEO_LEVEL = "4.2"
|
| 21 |
-
YOUTUBE_VIDEO_PIX_FMT = "yuv420p"
|
| 22 |
-
|
| 23 |
-
YOUTUBE_AUDIO_CODEC = "aac"
|
| 24 |
-
YOUTUBE_AUDIO_BITRATE = "320k"
|
| 25 |
-
YOUTUBE_AUDIO_SAMPLE_RATE = 48000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
configs/inference/musetalk.yaml
DELETED
|
@@ -1,42 +0,0 @@
|
|
| 1 |
-
# MuseTalk V1.5 Inference Configuration
|
| 2 |
-
# Simplified config for integration with OutofLipSync
|
| 3 |
-
|
| 4 |
-
version: "v15"
|
| 5 |
-
model_name: "musetalk"
|
| 6 |
-
use_float16: true
|
| 7 |
-
batch_size: 8
|
| 8 |
-
fps: 25
|
| 9 |
-
audio_padding_length_left: 2
|
| 10 |
-
audio_padding_length_right: 2
|
| 11 |
-
bbox_shift: 0
|
| 12 |
-
extra_margin: 10
|
| 13 |
-
left_cheek_width: 90
|
| 14 |
-
right_cheek_width: 90
|
| 15 |
-
parsing_mode: "jaw"
|
| 16 |
-
|
| 17 |
-
# Model paths (relative to checkpoints directory)
|
| 18 |
-
unet_config: "./checkpoints/musetalkV15/musetalk.json"
|
| 19 |
-
unet_model: "./checkpoints/musetalkV15/unet.pth"
|
| 20 |
-
vae_model: "./checkpoints/sd-vae-ft-mse"
|
| 21 |
-
whisper_model: "./checkpoints/whisper-tiny"
|
| 22 |
-
|
| 23 |
-
# Input paths (to be overridden programmatically)
|
| 24 |
-
video_path: "data/video/input.mp4"
|
| 25 |
-
audio_path: "data/audio/input.wav"
|
| 26 |
-
result_dir: "./results"
|
| 27 |
-
output_vid_name: null
|
| 28 |
-
|
| 29 |
-
# Device settings
|
| 30 |
-
gpu_id: 0
|
| 31 |
-
device: "cuda"
|
| 32 |
-
|
| 33 |
-
# Optional: Use saved coordinates to skip landmark detection
|
| 34 |
-
use_saved_coord: false
|
| 35 |
-
saved_coord: false
|
| 36 |
-
|
| 37 |
-
# Audio processing
|
| 38 |
-
audio_sample_rate: 16000
|
| 39 |
-
|
| 40 |
-
# Video processing
|
| 41 |
-
crop_size: 256
|
| 42 |
-
interpolation_mode: "lanczos"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
configs/scheduler_config.json
DELETED
|
@@ -1,12 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"_class_name": "DDIMScheduler",
|
| 3 |
-
"beta_end": 0.012,
|
| 4 |
-
"beta_schedule": "scaled_linear",
|
| 5 |
-
"beta_start": 0.00085,
|
| 6 |
-
"clip_sample": false,
|
| 7 |
-
"num_train_timesteps": 1000,
|
| 8 |
-
"set_alpha_to_one": false,
|
| 9 |
-
"steps_offset": 1,
|
| 10 |
-
"trained_betas": null,
|
| 11 |
-
"skip_prk_steps": true
|
| 12 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
configs/unet/stage2_512.yaml
DELETED
|
@@ -1,99 +0,0 @@
|
|
| 1 |
-
data:
|
| 2 |
-
syncnet_config_path: configs/syncnet/syncnet_16_pixel_attn.yaml
|
| 3 |
-
train_output_dir: debug/unet
|
| 4 |
-
train_fileslist: /mnt/bn/maliva-gen-ai-v2/chunyu.li/fileslist/data_v10_core.txt
|
| 5 |
-
train_data_dir: ""
|
| 6 |
-
audio_embeds_cache_dir: /mnt/bn/maliva-gen-ai-v2/chunyu.li/audio_cache/embeds
|
| 7 |
-
audio_mel_cache_dir: /mnt/bn/maliva-gen-ai-v2/chunyu.li/audio_cache/mel
|
| 8 |
-
|
| 9 |
-
val_video_path: assets/demo1_video.mp4
|
| 10 |
-
val_audio_path: assets/demo1_audio.wav
|
| 11 |
-
batch_size: 1
|
| 12 |
-
num_workers: 12
|
| 13 |
-
num_frames: 16
|
| 14 |
-
resolution: 512
|
| 15 |
-
mask_image_path: latentsync/utils/mask.png
|
| 16 |
-
audio_sample_rate: 16000
|
| 17 |
-
video_fps: 25
|
| 18 |
-
audio_feat_length: [2, 2]
|
| 19 |
-
|
| 20 |
-
ckpt:
|
| 21 |
-
resume_ckpt_path: checkpoints/latentsync_unet.pt
|
| 22 |
-
save_ckpt_steps: 10000
|
| 23 |
-
|
| 24 |
-
run:
|
| 25 |
-
pixel_space_supervise: true
|
| 26 |
-
use_syncnet: true
|
| 27 |
-
sync_loss_weight: 0.05
|
| 28 |
-
perceptual_loss_weight: 0.1
|
| 29 |
-
recon_loss_weight: 1
|
| 30 |
-
guidance_scale: 1.5
|
| 31 |
-
trepa_loss_weight: 10
|
| 32 |
-
inference_steps: 20
|
| 33 |
-
trainable_modules:
|
| 34 |
-
- motion_modules.
|
| 35 |
-
- attentions.
|
| 36 |
-
seed: 1247
|
| 37 |
-
use_mixed_noise: true
|
| 38 |
-
mixed_noise_alpha: 1
|
| 39 |
-
mixed_precision_training: true
|
| 40 |
-
enable_gradient_checkpointing: true
|
| 41 |
-
max_train_steps: 10000000
|
| 42 |
-
max_train_epochs: -1
|
| 43 |
-
|
| 44 |
-
optimizer:
|
| 45 |
-
lr: 1e-5
|
| 46 |
-
scale_lr: false
|
| 47 |
-
max_grad_norm: 1.0
|
| 48 |
-
lr_scheduler: constant
|
| 49 |
-
lr_warmup_steps: 0
|
| 50 |
-
|
| 51 |
-
model:
|
| 52 |
-
act_fn: silu
|
| 53 |
-
add_audio_layer: true
|
| 54 |
-
attention_head_dim: 8
|
| 55 |
-
block_out_channels: [320, 640, 1280, 1280]
|
| 56 |
-
center_input_sample: false
|
| 57 |
-
cross_attention_dim: 384
|
| 58 |
-
down_block_types:
|
| 59 |
-
[
|
| 60 |
-
"CrossAttnDownBlock3D",
|
| 61 |
-
"CrossAttnDownBlock3D",
|
| 62 |
-
"CrossAttnDownBlock3D",
|
| 63 |
-
"DownBlock3D",
|
| 64 |
-
]
|
| 65 |
-
mid_block_type: UNetMidBlock3DCrossAttn
|
| 66 |
-
up_block_types:
|
| 67 |
-
[
|
| 68 |
-
"UpBlock3D",
|
| 69 |
-
"CrossAttnUpBlock3D",
|
| 70 |
-
"CrossAttnUpBlock3D",
|
| 71 |
-
"CrossAttnUpBlock3D",
|
| 72 |
-
]
|
| 73 |
-
downsample_padding: 1
|
| 74 |
-
flip_sin_to_cos: true
|
| 75 |
-
freq_shift: 0
|
| 76 |
-
in_channels: 13
|
| 77 |
-
layers_per_block: 2
|
| 78 |
-
mid_block_scale_factor: 1
|
| 79 |
-
norm_eps: 1e-5
|
| 80 |
-
norm_num_groups: 32
|
| 81 |
-
out_channels: 4
|
| 82 |
-
sample_size: 64
|
| 83 |
-
resnet_time_scale_shift: default
|
| 84 |
-
|
| 85 |
-
use_motion_module: true
|
| 86 |
-
motion_module_resolutions: [1, 2, 4, 8]
|
| 87 |
-
motion_module_mid_block: false
|
| 88 |
-
motion_module_decoder_only: false
|
| 89 |
-
motion_module_type: Vanilla
|
| 90 |
-
motion_module_kwargs:
|
| 91 |
-
num_attention_heads: 8
|
| 92 |
-
num_transformer_block: 1
|
| 93 |
-
attention_block_types:
|
| 94 |
-
- Temporal_Self
|
| 95 |
-
- Temporal_Self
|
| 96 |
-
temporal_position_encoding: true
|
| 97 |
-
temporal_position_encoding_max_len: 24
|
| 98 |
-
temporal_attention_dim_div: 1
|
| 99 |
-
zero_initialize: true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docker-compose.yml
DELETED
|
@@ -1,19 +0,0 @@
|
|
| 1 |
-
version: '3.8'
|
| 2 |
-
|
| 3 |
-
services:
|
| 4 |
-
silent-wolf:
|
| 5 |
-
build: .
|
| 6 |
-
ports:
|
| 7 |
-
- "7860:7860"
|
| 8 |
-
volumes:
|
| 9 |
-
- ./processed_results:/app/processed_results
|
| 10 |
-
deploy:
|
| 11 |
-
resources:
|
| 12 |
-
reservations:
|
| 13 |
-
devices:
|
| 14 |
-
- driver: nvidia
|
| 15 |
-
count: all
|
| 16 |
-
capabilities: [gpu]
|
| 17 |
-
environment:
|
| 18 |
-
- PYTHONUNBUFFERED=1
|
| 19 |
-
- PROCESSED_RESULTS=/app/processed_results
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
download_checkpoints.sh
DELETED
|
@@ -1,18 +0,0 @@
|
|
| 1 |
-
#!/bin/bash
|
| 2 |
-
|
| 3 |
-
set -e
|
| 4 |
-
|
| 5 |
-
echo "Downloading LatentSync v1.6 checkpoints..."
|
| 6 |
-
|
| 7 |
-
# Create checkpoints directory
|
| 8 |
-
mkdir -p checkpoints
|
| 9 |
-
mkdir -p checkpoints/whisper
|
| 10 |
-
|
| 11 |
-
# Download from ByteDance/LatentSync-1.6
|
| 12 |
-
echo "Downloading latentsync_unet.pt..."
|
| 13 |
-
huggingface-cli download ByteDance/LatentSync-1.6 latentsync_unet.pt --local-dir checkpoints
|
| 14 |
-
|
| 15 |
-
echo "Downloading whisper model..."
|
| 16 |
-
huggingface-cli download ByteDance/LatentSync-1.6 whisper/tiny.pt --local-dir checkpoints/whisper
|
| 17 |
-
|
| 18 |
-
echo "Checkpoints downloaded successfully!"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
download_musetalk_models.py
DELETED
|
@@ -1,203 +0,0 @@
|
|
| 1 |
-
"""Download MuseTalk V1.5 models - On-demand download manager"""
|
| 2 |
-
|
| 3 |
-
from huggingface_hub import snapshot_download, hf_hub_download
|
| 4 |
-
import os
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
def download_musetalk_models(
|
| 8 |
-
checkpoints_dir: str = "./checkpoints", force: bool = False
|
| 9 |
-
):
|
| 10 |
-
"""Download MuseTalk V1.5 models if not present
|
| 11 |
-
|
| 12 |
-
Args:
|
| 13 |
-
checkpoints_dir: Directory to store checkpoints
|
| 14 |
-
force: Force re-download even if files exist
|
| 15 |
-
"""
|
| 16 |
-
|
| 17 |
-
print(f"\n{'=' * 60}")
|
| 18 |
-
print(f"DOWNLOADING MUSETALK V1.5 MODELS")
|
| 19 |
-
print(f"{'=' * 60}\n")
|
| 20 |
-
|
| 21 |
-
os.makedirs(f"{checkpoints_dir}/musetalkV15", exist_ok=True)
|
| 22 |
-
|
| 23 |
-
downloaded_count = 0
|
| 24 |
-
total_count = 6
|
| 25 |
-
|
| 26 |
-
# MuseTalk V1.5 UNet and config
|
| 27 |
-
if force or not os.path.exists(f"{checkpoints_dir}/musetalkV15/unet.pth"):
|
| 28 |
-
print(" Downloading MuseTalk V1.5 UNet...")
|
| 29 |
-
try:
|
| 30 |
-
hf_hub_download(
|
| 31 |
-
repo_id="TMElyralab/MuseTalk",
|
| 32 |
-
filename="models/musetalkV15/unet.pth",
|
| 33 |
-
local_dir=checkpoints_dir,
|
| 34 |
-
)
|
| 35 |
-
downloaded_count += 1
|
| 36 |
-
print(" β UNet downloaded")
|
| 37 |
-
except Exception as e:
|
| 38 |
-
print(f" β Failed to download UNet: {e}")
|
| 39 |
-
else:
|
| 40 |
-
print(" β UNet already exists, skipping")
|
| 41 |
-
|
| 42 |
-
if force or not os.path.exists(f"{checkpoints_dir}/musetalkV15/musetalk.json"):
|
| 43 |
-
print(" Downloading MuseTalk V1.5 config...")
|
| 44 |
-
try:
|
| 45 |
-
hf_hub_download(
|
| 46 |
-
repo_id="TMElyralab/MuseTalk",
|
| 47 |
-
filename="models/musetalkV15/musetalk.json",
|
| 48 |
-
local_dir=checkpoints_dir,
|
| 49 |
-
)
|
| 50 |
-
downloaded_count += 1
|
| 51 |
-
print(" β Config downloaded")
|
| 52 |
-
except Exception as e:
|
| 53 |
-
print(f" β Failed to download config: {e}")
|
| 54 |
-
else:
|
| 55 |
-
print(" β Config already exists, skipping")
|
| 56 |
-
|
| 57 |
-
# SD-VAE-FT-MSE
|
| 58 |
-
if force or not os.path.exists(f"{checkpoints_dir}/sd-vae-ft-mse/config.json"):
|
| 59 |
-
print(" Downloading SD-VAE-FT-MSE...")
|
| 60 |
-
try:
|
| 61 |
-
snapshot_download(
|
| 62 |
-
repo_id="stabilityai/sd-vae-ft-mse",
|
| 63 |
-
local_dir=f"{checkpoints_dir}/sd-vae-ft-mse",
|
| 64 |
-
)
|
| 65 |
-
downloaded_count += 1
|
| 66 |
-
print(" β SD-VAE-FT-MSE downloaded")
|
| 67 |
-
except Exception as e:
|
| 68 |
-
print(f" β Failed to download SD-VAE: {e}")
|
| 69 |
-
else:
|
| 70 |
-
print(" β SD-VAE already exists, skipping")
|
| 71 |
-
|
| 72 |
-
# Whisper-Tiny
|
| 73 |
-
if force or not os.path.exists(f"{checkpoints_dir}/whisper-tiny/config.json"):
|
| 74 |
-
print(" Downloading Whisper-Tiny...")
|
| 75 |
-
try:
|
| 76 |
-
snapshot_download(
|
| 77 |
-
repo_id="openai/whisper-tiny",
|
| 78 |
-
local_dir=f"{checkpoints_dir}/whisper-tiny",
|
| 79 |
-
)
|
| 80 |
-
downloaded_count += 1
|
| 81 |
-
print(" β Whisper-Tiny downloaded")
|
| 82 |
-
except Exception as e:
|
| 83 |
-
print(f" β Failed to download Whisper: {e}")
|
| 84 |
-
else:
|
| 85 |
-
print(" β Whisper already exists, skipping")
|
| 86 |
-
|
| 87 |
-
# Face Parsing models
|
| 88 |
-
if force or not os.path.exists(
|
| 89 |
-
f"{checkpoints_dir}/face-parse-bisent/79999_iter.pth"
|
| 90 |
-
):
|
| 91 |
-
print(" Downloading Face Parsing model...")
|
| 92 |
-
try:
|
| 93 |
-
hf_hub_download(
|
| 94 |
-
repo_id="TMElyralab/MuseTalk",
|
| 95 |
-
filename="models/face-parse-bisent/79999_iter.pth",
|
| 96 |
-
local_dir=f"{checkpoints_dir}/face-parse-bisent",
|
| 97 |
-
)
|
| 98 |
-
downloaded_count += 1
|
| 99 |
-
print(" β Face Parsing model downloaded")
|
| 100 |
-
except Exception as e:
|
| 101 |
-
print(f" β Failed to download Face Parsing: {e}")
|
| 102 |
-
else:
|
| 103 |
-
print(" β Face Parsing model already exists, skipping")
|
| 104 |
-
|
| 105 |
-
if force or not os.path.exists(
|
| 106 |
-
f"{checkpoints_dir}/face-parse-bisent/resnet18-5c106cde.pth"
|
| 107 |
-
):
|
| 108 |
-
try:
|
| 109 |
-
hf_hub_download(
|
| 110 |
-
repo_id="TMElyralab/MuseTalk",
|
| 111 |
-
filename="models/face-parse-bisent/resnet18-5c106cde.pth",
|
| 112 |
-
local_dir=f"{checkpoints_dir}/face-parse-bisent",
|
| 113 |
-
)
|
| 114 |
-
downloaded_count += 1
|
| 115 |
-
print(" β ResNet18 downloaded")
|
| 116 |
-
except Exception as e:
|
| 117 |
-
print(f" β Failed to download ResNet18: {e}")
|
| 118 |
-
else:
|
| 119 |
-
print(" β ResNet18 already exists, skipping")
|
| 120 |
-
|
| 121 |
-
# DWPose models (optional - only download if mmpose is installed)
|
| 122 |
-
try:
|
| 123 |
-
import mmpose
|
| 124 |
-
|
| 125 |
-
if force or not os.path.exists(f"{checkpoints_dir}/dwpose/dw-ll_ucoco_384.pth"):
|
| 126 |
-
print(" Downloading DWPose checkpoint...")
|
| 127 |
-
try:
|
| 128 |
-
hf_hub_download(
|
| 129 |
-
repo_id="TMElyralab/MuseTalk",
|
| 130 |
-
filename="models/dwpose/dw-ll_ucoco_384.pth",
|
| 131 |
-
local_dir=f"{checkpoints_dir}/dwpose",
|
| 132 |
-
)
|
| 133 |
-
downloaded_count += 1
|
| 134 |
-
print(" β DWPose checkpoint downloaded")
|
| 135 |
-
except Exception as e:
|
| 136 |
-
print(f" β Failed to download DWPose: {e}")
|
| 137 |
-
else:
|
| 138 |
-
print(" β DWPose checkpoint already exists, skipping")
|
| 139 |
-
except ImportError:
|
| 140 |
-
print(" β mmpose not installed, skipping DWPose download")
|
| 141 |
-
|
| 142 |
-
print(f"\n{'=' * 60}")
|
| 143 |
-
print(f"DOWNLOAD COMPLETE: {downloaded_count}/{total_count} models downloaded")
|
| 144 |
-
print(f"{'=' * 60}\n")
|
| 145 |
-
|
| 146 |
-
return downloaded_count > 0
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
def check_models(checkpoints_dir: str = "./checkpoints") -> dict:
|
| 150 |
-
"""Check which MuseTalk models are available
|
| 151 |
-
|
| 152 |
-
Returns:
|
| 153 |
-
Dict with status of each model
|
| 154 |
-
"""
|
| 155 |
-
status = {
|
| 156 |
-
"unet": os.path.exists(f"{checkpoints_dir}/musetalkV15/unet.pth"),
|
| 157 |
-
"unet_config": os.path.exists(f"{checkpoints_dir}/musetalkV15/musetalk.json"),
|
| 158 |
-
"vae": os.path.exists(f"{checkpoints_dir}/sd-vae-ft-mse/config.json"),
|
| 159 |
-
"whisper": os.path.exists(f"{checkpoints_dir}/whisper-tiny/config.json"),
|
| 160 |
-
"face_parsing": os.path.exists(
|
| 161 |
-
f"{checkpoints_dir}/face-parse-bisent/79999_iter.pth"
|
| 162 |
-
),
|
| 163 |
-
"resnet18": os.path.exists(
|
| 164 |
-
f"{checkpoints_dir}/face-parse-bisent/resnet18-5c106cde.pth"
|
| 165 |
-
),
|
| 166 |
-
"dwpose": os.path.exists(f"{checkpoints_dir}/dwpose/dw-ll_ucoco_384.pth"),
|
| 167 |
-
}
|
| 168 |
-
|
| 169 |
-
missing = [k for k, v in status.items() if not v]
|
| 170 |
-
available = [k for k, v in status.items() if v]
|
| 171 |
-
|
| 172 |
-
print(f"\nModels Status:")
|
| 173 |
-
print(f" Available: {len(available)}/{len(status)}")
|
| 174 |
-
print(f" Missing: {', '.join(missing) if missing else 'None'}")
|
| 175 |
-
|
| 176 |
-
return status
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
if __name__ == "__main__":
|
| 180 |
-
import argparse
|
| 181 |
-
|
| 182 |
-
parser = argparse.ArgumentParser(description="Download MuseTalk V1.5 models")
|
| 183 |
-
parser.add_argument(
|
| 184 |
-
"--check",
|
| 185 |
-
"-c",
|
| 186 |
-
action="store_true",
|
| 187 |
-
help="Check model status without downloading",
|
| 188 |
-
)
|
| 189 |
-
parser.add_argument(
|
| 190 |
-
"--force", "-f", action="store_true", help="Force re-download all models"
|
| 191 |
-
)
|
| 192 |
-
parser.add_argument(
|
| 193 |
-
"--checkpoint-dir",
|
| 194 |
-
default="./checkpoints",
|
| 195 |
-
help="Directory to store checkpoints",
|
| 196 |
-
)
|
| 197 |
-
|
| 198 |
-
args = parser.parse_args()
|
| 199 |
-
|
| 200 |
-
if args.check:
|
| 201 |
-
check_models(args.checkpoint_dir)
|
| 202 |
-
else:
|
| 203 |
-
download_musetalk_models(args.checkpoint_dir, force=args.force)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/detectors/README.md
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
# Face detector
|
| 2 |
-
|
| 3 |
-
This face detector is adapted from `https://github.com/cs-giung/face-detection-pytorch`.
|
|
|
|
|
|
|
|
|
|
|
|
eval/detectors/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
from .s3fd import S3FD
|
|
|
|
|
|
eval/detectors/s3fd/__init__.py
DELETED
|
@@ -1,63 +0,0 @@
|
|
| 1 |
-
import time
|
| 2 |
-
import numpy as np
|
| 3 |
-
import cv2
|
| 4 |
-
import torch
|
| 5 |
-
from torchvision import transforms
|
| 6 |
-
from .nets import S3FDNet
|
| 7 |
-
from .box_utils import nms_
|
| 8 |
-
from latentsync.utils.util import check_model_and_download
|
| 9 |
-
|
| 10 |
-
PATH_WEIGHT = "checkpoints/auxiliary/sfd_face.pth"
|
| 11 |
-
img_mean = np.array([104.0, 117.0, 123.0])[:, np.newaxis, np.newaxis].astype("float32")
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
class S3FD:
|
| 15 |
-
|
| 16 |
-
def __init__(self, device="cuda"):
|
| 17 |
-
|
| 18 |
-
tstamp = time.time()
|
| 19 |
-
self.device = device
|
| 20 |
-
|
| 21 |
-
print("[S3FD] loading with", self.device)
|
| 22 |
-
self.net = S3FDNet(device=self.device).to(self.device)
|
| 23 |
-
check_model_and_download(PATH_WEIGHT)
|
| 24 |
-
state_dict = torch.load(PATH_WEIGHT, map_location=self.device, weights_only=True)
|
| 25 |
-
self.net.load_state_dict(state_dict)
|
| 26 |
-
self.net.eval()
|
| 27 |
-
print("[S3FD] finished loading (%.4f sec)" % (time.time() - tstamp))
|
| 28 |
-
|
| 29 |
-
def detect_faces(self, image, conf_th=0.8, scales=[1]):
|
| 30 |
-
|
| 31 |
-
w, h = image.shape[1], image.shape[0]
|
| 32 |
-
|
| 33 |
-
bboxes = np.empty(shape=(0, 5))
|
| 34 |
-
|
| 35 |
-
with torch.no_grad():
|
| 36 |
-
for s in scales:
|
| 37 |
-
scaled_img = cv2.resize(image, dsize=(0, 0), fx=s, fy=s, interpolation=cv2.INTER_LINEAR)
|
| 38 |
-
|
| 39 |
-
scaled_img = np.swapaxes(scaled_img, 1, 2)
|
| 40 |
-
scaled_img = np.swapaxes(scaled_img, 1, 0)
|
| 41 |
-
scaled_img = scaled_img[[2, 1, 0], :, :]
|
| 42 |
-
scaled_img = scaled_img.astype("float32")
|
| 43 |
-
scaled_img -= img_mean
|
| 44 |
-
scaled_img = scaled_img[[2, 1, 0], :, :]
|
| 45 |
-
x = torch.from_numpy(scaled_img).unsqueeze(0).to(self.device)
|
| 46 |
-
y = self.net(x)
|
| 47 |
-
|
| 48 |
-
detections = y.data
|
| 49 |
-
scale = torch.Tensor([w, h, w, h])
|
| 50 |
-
|
| 51 |
-
for i in range(detections.size(1)):
|
| 52 |
-
j = 0
|
| 53 |
-
while detections[0, i, j, 0] > conf_th:
|
| 54 |
-
score = detections[0, i, j, 0]
|
| 55 |
-
pt = (detections[0, i, j, 1:] * scale).cpu().numpy()
|
| 56 |
-
bbox = (pt[0], pt[1], pt[2], pt[3], score)
|
| 57 |
-
bboxes = np.vstack((bboxes, bbox))
|
| 58 |
-
j += 1
|
| 59 |
-
|
| 60 |
-
keep = nms_(bboxes, 0.1)
|
| 61 |
-
bboxes = bboxes[keep]
|
| 62 |
-
|
| 63 |
-
return bboxes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/detectors/s3fd/box_utils.py
DELETED
|
@@ -1,221 +0,0 @@
|
|
| 1 |
-
import numpy as np
|
| 2 |
-
from itertools import product as product
|
| 3 |
-
import torch
|
| 4 |
-
from torch.autograd import Function
|
| 5 |
-
import warnings
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
def nms_(dets, thresh):
|
| 9 |
-
"""
|
| 10 |
-
Courtesy of Ross Girshick
|
| 11 |
-
[https://github.com/rbgirshick/py-faster-rcnn/blob/master/lib/nms/py_cpu_nms.py]
|
| 12 |
-
"""
|
| 13 |
-
x1 = dets[:, 0]
|
| 14 |
-
y1 = dets[:, 1]
|
| 15 |
-
x2 = dets[:, 2]
|
| 16 |
-
y2 = dets[:, 3]
|
| 17 |
-
scores = dets[:, 4]
|
| 18 |
-
|
| 19 |
-
areas = (x2 - x1) * (y2 - y1)
|
| 20 |
-
order = scores.argsort()[::-1]
|
| 21 |
-
|
| 22 |
-
keep = []
|
| 23 |
-
while order.size > 0:
|
| 24 |
-
i = order[0]
|
| 25 |
-
keep.append(int(i))
|
| 26 |
-
xx1 = np.maximum(x1[i], x1[order[1:]])
|
| 27 |
-
yy1 = np.maximum(y1[i], y1[order[1:]])
|
| 28 |
-
xx2 = np.minimum(x2[i], x2[order[1:]])
|
| 29 |
-
yy2 = np.minimum(y2[i], y2[order[1:]])
|
| 30 |
-
|
| 31 |
-
w = np.maximum(0.0, xx2 - xx1)
|
| 32 |
-
h = np.maximum(0.0, yy2 - yy1)
|
| 33 |
-
inter = w * h
|
| 34 |
-
ovr = inter / (areas[i] + areas[order[1:]] - inter)
|
| 35 |
-
|
| 36 |
-
inds = np.where(ovr <= thresh)[0]
|
| 37 |
-
order = order[inds + 1]
|
| 38 |
-
|
| 39 |
-
return np.array(keep).astype(np.int32)
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
def decode(loc, priors, variances):
|
| 43 |
-
"""Decode locations from predictions using priors to undo
|
| 44 |
-
the encoding we did for offset regression at train time.
|
| 45 |
-
Args:
|
| 46 |
-
loc (tensor): location predictions for loc layers,
|
| 47 |
-
Shape: [num_priors,4]
|
| 48 |
-
priors (tensor): Prior boxes in center-offset form.
|
| 49 |
-
Shape: [num_priors,4].
|
| 50 |
-
variances: (list[float]) Variances of priorboxes
|
| 51 |
-
Return:
|
| 52 |
-
decoded bounding box predictions
|
| 53 |
-
"""
|
| 54 |
-
|
| 55 |
-
boxes = torch.cat((
|
| 56 |
-
priors[:, :2] + loc[:, :2] * variances[0] * priors[:, 2:],
|
| 57 |
-
priors[:, 2:] * torch.exp(loc[:, 2:] * variances[1])), 1)
|
| 58 |
-
boxes[:, :2] -= boxes[:, 2:] / 2
|
| 59 |
-
boxes[:, 2:] += boxes[:, :2]
|
| 60 |
-
return boxes
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
def nms(boxes, scores, overlap=0.5, top_k=200):
|
| 64 |
-
"""Apply non-maximum suppression at test time to avoid detecting too many
|
| 65 |
-
overlapping bounding boxes for a given object.
|
| 66 |
-
Args:
|
| 67 |
-
boxes: (tensor) The location preds for the img, Shape: [num_priors,4].
|
| 68 |
-
scores: (tensor) The class predscores for the img, Shape:[num_priors].
|
| 69 |
-
overlap: (float) The overlap thresh for suppressing unnecessary boxes.
|
| 70 |
-
top_k: (int) The Maximum number of box preds to consider.
|
| 71 |
-
Return:
|
| 72 |
-
The indices of the kept boxes with respect to num_priors.
|
| 73 |
-
"""
|
| 74 |
-
|
| 75 |
-
keep = scores.new(scores.size(0)).zero_().long()
|
| 76 |
-
if boxes.numel() == 0:
|
| 77 |
-
return keep, 0
|
| 78 |
-
x1 = boxes[:, 0]
|
| 79 |
-
y1 = boxes[:, 1]
|
| 80 |
-
x2 = boxes[:, 2]
|
| 81 |
-
y2 = boxes[:, 3]
|
| 82 |
-
area = torch.mul(x2 - x1, y2 - y1)
|
| 83 |
-
v, idx = scores.sort(0) # sort in ascending order
|
| 84 |
-
# I = I[v >= 0.01]
|
| 85 |
-
idx = idx[-top_k:] # indices of the top-k largest vals
|
| 86 |
-
xx1 = boxes.new()
|
| 87 |
-
yy1 = boxes.new()
|
| 88 |
-
xx2 = boxes.new()
|
| 89 |
-
yy2 = boxes.new()
|
| 90 |
-
w = boxes.new()
|
| 91 |
-
h = boxes.new()
|
| 92 |
-
|
| 93 |
-
# keep = torch.Tensor()
|
| 94 |
-
count = 0
|
| 95 |
-
while idx.numel() > 0:
|
| 96 |
-
i = idx[-1] # index of current largest val
|
| 97 |
-
# keep.append(i)
|
| 98 |
-
keep[count] = i
|
| 99 |
-
count += 1
|
| 100 |
-
if idx.size(0) == 1:
|
| 101 |
-
break
|
| 102 |
-
idx = idx[:-1] # remove kept element from view
|
| 103 |
-
# load bboxes of next highest vals
|
| 104 |
-
with warnings.catch_warnings():
|
| 105 |
-
# Ignore UserWarning within this block
|
| 106 |
-
warnings.simplefilter("ignore", category=UserWarning)
|
| 107 |
-
torch.index_select(x1, 0, idx, out=xx1)
|
| 108 |
-
torch.index_select(y1, 0, idx, out=yy1)
|
| 109 |
-
torch.index_select(x2, 0, idx, out=xx2)
|
| 110 |
-
torch.index_select(y2, 0, idx, out=yy2)
|
| 111 |
-
# store element-wise max with next highest score
|
| 112 |
-
xx1 = torch.clamp(xx1, min=x1[i])
|
| 113 |
-
yy1 = torch.clamp(yy1, min=y1[i])
|
| 114 |
-
xx2 = torch.clamp(xx2, max=x2[i])
|
| 115 |
-
yy2 = torch.clamp(yy2, max=y2[i])
|
| 116 |
-
w.resize_as_(xx2)
|
| 117 |
-
h.resize_as_(yy2)
|
| 118 |
-
w = xx2 - xx1
|
| 119 |
-
h = yy2 - yy1
|
| 120 |
-
# check sizes of xx1 and xx2.. after each iteration
|
| 121 |
-
w = torch.clamp(w, min=0.0)
|
| 122 |
-
h = torch.clamp(h, min=0.0)
|
| 123 |
-
inter = w * h
|
| 124 |
-
# IoU = i / (area(a) + area(b) - i)
|
| 125 |
-
rem_areas = torch.index_select(area, 0, idx) # load remaining areas)
|
| 126 |
-
union = (rem_areas - inter) + area[i]
|
| 127 |
-
IoU = inter / union # store result in iou
|
| 128 |
-
# keep only elements with an IoU <= overlap
|
| 129 |
-
idx = idx[IoU.le(overlap)]
|
| 130 |
-
return keep, count
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
class Detect(object):
|
| 134 |
-
|
| 135 |
-
def __init__(self, num_classes=2,
|
| 136 |
-
top_k=750, nms_thresh=0.3, conf_thresh=0.05,
|
| 137 |
-
variance=[0.1, 0.2], nms_top_k=5000):
|
| 138 |
-
|
| 139 |
-
self.num_classes = num_classes
|
| 140 |
-
self.top_k = top_k
|
| 141 |
-
self.nms_thresh = nms_thresh
|
| 142 |
-
self.conf_thresh = conf_thresh
|
| 143 |
-
self.variance = variance
|
| 144 |
-
self.nms_top_k = nms_top_k
|
| 145 |
-
|
| 146 |
-
def forward(self, loc_data, conf_data, prior_data):
|
| 147 |
-
|
| 148 |
-
num = loc_data.size(0)
|
| 149 |
-
num_priors = prior_data.size(0)
|
| 150 |
-
|
| 151 |
-
conf_preds = conf_data.view(num, num_priors, self.num_classes).transpose(2, 1)
|
| 152 |
-
batch_priors = prior_data.view(-1, num_priors, 4).expand(num, num_priors, 4)
|
| 153 |
-
batch_priors = batch_priors.contiguous().view(-1, 4)
|
| 154 |
-
|
| 155 |
-
decoded_boxes = decode(loc_data.view(-1, 4), batch_priors, self.variance)
|
| 156 |
-
decoded_boxes = decoded_boxes.view(num, num_priors, 4)
|
| 157 |
-
|
| 158 |
-
output = torch.zeros(num, self.num_classes, self.top_k, 5)
|
| 159 |
-
|
| 160 |
-
for i in range(num):
|
| 161 |
-
boxes = decoded_boxes[i].clone()
|
| 162 |
-
conf_scores = conf_preds[i].clone()
|
| 163 |
-
|
| 164 |
-
for cl in range(1, self.num_classes):
|
| 165 |
-
c_mask = conf_scores[cl].gt(self.conf_thresh)
|
| 166 |
-
scores = conf_scores[cl][c_mask]
|
| 167 |
-
|
| 168 |
-
if scores.dim() == 0:
|
| 169 |
-
continue
|
| 170 |
-
l_mask = c_mask.unsqueeze(1).expand_as(boxes)
|
| 171 |
-
boxes_ = boxes[l_mask].view(-1, 4)
|
| 172 |
-
ids, count = nms(boxes_, scores, self.nms_thresh, self.nms_top_k)
|
| 173 |
-
count = count if count < self.top_k else self.top_k
|
| 174 |
-
|
| 175 |
-
output[i, cl, :count] = torch.cat((scores[ids[:count]].unsqueeze(1), boxes_[ids[:count]]), 1)
|
| 176 |
-
|
| 177 |
-
return output
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
class PriorBox(object):
|
| 181 |
-
|
| 182 |
-
def __init__(self, input_size, feature_maps,
|
| 183 |
-
variance=[0.1, 0.2],
|
| 184 |
-
min_sizes=[16, 32, 64, 128, 256, 512],
|
| 185 |
-
steps=[4, 8, 16, 32, 64, 128],
|
| 186 |
-
clip=False):
|
| 187 |
-
|
| 188 |
-
super(PriorBox, self).__init__()
|
| 189 |
-
|
| 190 |
-
self.imh = input_size[0]
|
| 191 |
-
self.imw = input_size[1]
|
| 192 |
-
self.feature_maps = feature_maps
|
| 193 |
-
|
| 194 |
-
self.variance = variance
|
| 195 |
-
self.min_sizes = min_sizes
|
| 196 |
-
self.steps = steps
|
| 197 |
-
self.clip = clip
|
| 198 |
-
|
| 199 |
-
def forward(self):
|
| 200 |
-
mean = []
|
| 201 |
-
for k, fmap in enumerate(self.feature_maps):
|
| 202 |
-
feath = fmap[0]
|
| 203 |
-
featw = fmap[1]
|
| 204 |
-
for i, j in product(range(feath), range(featw)):
|
| 205 |
-
f_kw = self.imw / self.steps[k]
|
| 206 |
-
f_kh = self.imh / self.steps[k]
|
| 207 |
-
|
| 208 |
-
cx = (j + 0.5) / f_kw
|
| 209 |
-
cy = (i + 0.5) / f_kh
|
| 210 |
-
|
| 211 |
-
s_kw = self.min_sizes[k] / self.imw
|
| 212 |
-
s_kh = self.min_sizes[k] / self.imh
|
| 213 |
-
|
| 214 |
-
mean += [cx, cy, s_kw, s_kh]
|
| 215 |
-
|
| 216 |
-
output = torch.FloatTensor(mean).view(-1, 4)
|
| 217 |
-
|
| 218 |
-
if self.clip:
|
| 219 |
-
output.clamp_(max=1, min=0)
|
| 220 |
-
|
| 221 |
-
return output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/detectors/s3fd/nets.py
DELETED
|
@@ -1,174 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
import torch.nn as nn
|
| 3 |
-
import torch.nn.functional as F
|
| 4 |
-
import torch.nn.init as init
|
| 5 |
-
from .box_utils import Detect, PriorBox
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
class L2Norm(nn.Module):
|
| 9 |
-
|
| 10 |
-
def __init__(self, n_channels, scale):
|
| 11 |
-
super(L2Norm, self).__init__()
|
| 12 |
-
self.n_channels = n_channels
|
| 13 |
-
self.gamma = scale or None
|
| 14 |
-
self.eps = 1e-10
|
| 15 |
-
self.weight = nn.Parameter(torch.Tensor(self.n_channels))
|
| 16 |
-
self.reset_parameters()
|
| 17 |
-
|
| 18 |
-
def reset_parameters(self):
|
| 19 |
-
init.constant_(self.weight, self.gamma)
|
| 20 |
-
|
| 21 |
-
def forward(self, x):
|
| 22 |
-
norm = x.pow(2).sum(dim=1, keepdim=True).sqrt() + self.eps
|
| 23 |
-
x = torch.div(x, norm)
|
| 24 |
-
out = self.weight.unsqueeze(0).unsqueeze(2).unsqueeze(3).expand_as(x) * x
|
| 25 |
-
return out
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
class S3FDNet(nn.Module):
|
| 29 |
-
|
| 30 |
-
def __init__(self, device='cuda'):
|
| 31 |
-
super(S3FDNet, self).__init__()
|
| 32 |
-
self.device = device
|
| 33 |
-
|
| 34 |
-
self.vgg = nn.ModuleList([
|
| 35 |
-
nn.Conv2d(3, 64, 3, 1, padding=1),
|
| 36 |
-
nn.ReLU(inplace=True),
|
| 37 |
-
nn.Conv2d(64, 64, 3, 1, padding=1),
|
| 38 |
-
nn.ReLU(inplace=True),
|
| 39 |
-
nn.MaxPool2d(2, 2),
|
| 40 |
-
|
| 41 |
-
nn.Conv2d(64, 128, 3, 1, padding=1),
|
| 42 |
-
nn.ReLU(inplace=True),
|
| 43 |
-
nn.Conv2d(128, 128, 3, 1, padding=1),
|
| 44 |
-
nn.ReLU(inplace=True),
|
| 45 |
-
nn.MaxPool2d(2, 2),
|
| 46 |
-
|
| 47 |
-
nn.Conv2d(128, 256, 3, 1, padding=1),
|
| 48 |
-
nn.ReLU(inplace=True),
|
| 49 |
-
nn.Conv2d(256, 256, 3, 1, padding=1),
|
| 50 |
-
nn.ReLU(inplace=True),
|
| 51 |
-
nn.Conv2d(256, 256, 3, 1, padding=1),
|
| 52 |
-
nn.ReLU(inplace=True),
|
| 53 |
-
nn.MaxPool2d(2, 2, ceil_mode=True),
|
| 54 |
-
|
| 55 |
-
nn.Conv2d(256, 512, 3, 1, padding=1),
|
| 56 |
-
nn.ReLU(inplace=True),
|
| 57 |
-
nn.Conv2d(512, 512, 3, 1, padding=1),
|
| 58 |
-
nn.ReLU(inplace=True),
|
| 59 |
-
nn.Conv2d(512, 512, 3, 1, padding=1),
|
| 60 |
-
nn.ReLU(inplace=True),
|
| 61 |
-
nn.MaxPool2d(2, 2),
|
| 62 |
-
|
| 63 |
-
nn.Conv2d(512, 512, 3, 1, padding=1),
|
| 64 |
-
nn.ReLU(inplace=True),
|
| 65 |
-
nn.Conv2d(512, 512, 3, 1, padding=1),
|
| 66 |
-
nn.ReLU(inplace=True),
|
| 67 |
-
nn.Conv2d(512, 512, 3, 1, padding=1),
|
| 68 |
-
nn.ReLU(inplace=True),
|
| 69 |
-
nn.MaxPool2d(2, 2),
|
| 70 |
-
|
| 71 |
-
nn.Conv2d(512, 1024, 3, 1, padding=6, dilation=6),
|
| 72 |
-
nn.ReLU(inplace=True),
|
| 73 |
-
nn.Conv2d(1024, 1024, 1, 1),
|
| 74 |
-
nn.ReLU(inplace=True),
|
| 75 |
-
])
|
| 76 |
-
|
| 77 |
-
self.L2Norm3_3 = L2Norm(256, 10)
|
| 78 |
-
self.L2Norm4_3 = L2Norm(512, 8)
|
| 79 |
-
self.L2Norm5_3 = L2Norm(512, 5)
|
| 80 |
-
|
| 81 |
-
self.extras = nn.ModuleList([
|
| 82 |
-
nn.Conv2d(1024, 256, 1, 1),
|
| 83 |
-
nn.Conv2d(256, 512, 3, 2, padding=1),
|
| 84 |
-
nn.Conv2d(512, 128, 1, 1),
|
| 85 |
-
nn.Conv2d(128, 256, 3, 2, padding=1),
|
| 86 |
-
])
|
| 87 |
-
|
| 88 |
-
self.loc = nn.ModuleList([
|
| 89 |
-
nn.Conv2d(256, 4, 3, 1, padding=1),
|
| 90 |
-
nn.Conv2d(512, 4, 3, 1, padding=1),
|
| 91 |
-
nn.Conv2d(512, 4, 3, 1, padding=1),
|
| 92 |
-
nn.Conv2d(1024, 4, 3, 1, padding=1),
|
| 93 |
-
nn.Conv2d(512, 4, 3, 1, padding=1),
|
| 94 |
-
nn.Conv2d(256, 4, 3, 1, padding=1),
|
| 95 |
-
])
|
| 96 |
-
|
| 97 |
-
self.conf = nn.ModuleList([
|
| 98 |
-
nn.Conv2d(256, 4, 3, 1, padding=1),
|
| 99 |
-
nn.Conv2d(512, 2, 3, 1, padding=1),
|
| 100 |
-
nn.Conv2d(512, 2, 3, 1, padding=1),
|
| 101 |
-
nn.Conv2d(1024, 2, 3, 1, padding=1),
|
| 102 |
-
nn.Conv2d(512, 2, 3, 1, padding=1),
|
| 103 |
-
nn.Conv2d(256, 2, 3, 1, padding=1),
|
| 104 |
-
])
|
| 105 |
-
|
| 106 |
-
self.softmax = nn.Softmax(dim=-1)
|
| 107 |
-
self.detect = Detect()
|
| 108 |
-
|
| 109 |
-
def forward(self, x):
|
| 110 |
-
size = x.size()[2:]
|
| 111 |
-
sources = list()
|
| 112 |
-
loc = list()
|
| 113 |
-
conf = list()
|
| 114 |
-
|
| 115 |
-
for k in range(16):
|
| 116 |
-
x = self.vgg[k](x)
|
| 117 |
-
s = self.L2Norm3_3(x)
|
| 118 |
-
sources.append(s)
|
| 119 |
-
|
| 120 |
-
for k in range(16, 23):
|
| 121 |
-
x = self.vgg[k](x)
|
| 122 |
-
s = self.L2Norm4_3(x)
|
| 123 |
-
sources.append(s)
|
| 124 |
-
|
| 125 |
-
for k in range(23, 30):
|
| 126 |
-
x = self.vgg[k](x)
|
| 127 |
-
s = self.L2Norm5_3(x)
|
| 128 |
-
sources.append(s)
|
| 129 |
-
|
| 130 |
-
for k in range(30, len(self.vgg)):
|
| 131 |
-
x = self.vgg[k](x)
|
| 132 |
-
sources.append(x)
|
| 133 |
-
|
| 134 |
-
# apply extra layers and cache source layer outputs
|
| 135 |
-
for k, v in enumerate(self.extras):
|
| 136 |
-
x = F.relu(v(x), inplace=True)
|
| 137 |
-
if k % 2 == 1:
|
| 138 |
-
sources.append(x)
|
| 139 |
-
|
| 140 |
-
# apply multibox head to source layers
|
| 141 |
-
loc_x = self.loc[0](sources[0])
|
| 142 |
-
conf_x = self.conf[0](sources[0])
|
| 143 |
-
|
| 144 |
-
max_conf, _ = torch.max(conf_x[:, 0:3, :, :], dim=1, keepdim=True)
|
| 145 |
-
conf_x = torch.cat((max_conf, conf_x[:, 3:, :, :]), dim=1)
|
| 146 |
-
|
| 147 |
-
loc.append(loc_x.permute(0, 2, 3, 1).contiguous())
|
| 148 |
-
conf.append(conf_x.permute(0, 2, 3, 1).contiguous())
|
| 149 |
-
|
| 150 |
-
for i in range(1, len(sources)):
|
| 151 |
-
x = sources[i]
|
| 152 |
-
conf.append(self.conf[i](x).permute(0, 2, 3, 1).contiguous())
|
| 153 |
-
loc.append(self.loc[i](x).permute(0, 2, 3, 1).contiguous())
|
| 154 |
-
|
| 155 |
-
features_maps = []
|
| 156 |
-
for i in range(len(loc)):
|
| 157 |
-
feat = []
|
| 158 |
-
feat += [loc[i].size(1), loc[i].size(2)]
|
| 159 |
-
features_maps += [feat]
|
| 160 |
-
|
| 161 |
-
loc = torch.cat([o.view(o.size(0), -1) for o in loc], 1)
|
| 162 |
-
conf = torch.cat([o.view(o.size(0), -1) for o in conf], 1)
|
| 163 |
-
|
| 164 |
-
with torch.no_grad():
|
| 165 |
-
self.priorbox = PriorBox(size, features_maps)
|
| 166 |
-
self.priors = self.priorbox.forward()
|
| 167 |
-
|
| 168 |
-
output = self.detect.forward(
|
| 169 |
-
loc.view(loc.size(0), -1, 4),
|
| 170 |
-
self.softmax(conf.view(conf.size(0), -1, 2)),
|
| 171 |
-
self.priors.type(type(x.data)).to(self.device)
|
| 172 |
-
)
|
| 173 |
-
|
| 174 |
-
return output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/draw_syncnet_lines.py
DELETED
|
@@ -1,64 +0,0 @@
|
|
| 1 |
-
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import torch
|
| 16 |
-
import matplotlib.pyplot as plt
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
class Chart:
|
| 20 |
-
def __init__(self):
|
| 21 |
-
self.loss_list = []
|
| 22 |
-
|
| 23 |
-
def add_ckpt(self, ckpt_path, line_name):
|
| 24 |
-
ckpt = torch.load(ckpt_path, map_location="cpu")
|
| 25 |
-
train_step_list = ckpt["train_step_list"]
|
| 26 |
-
train_loss_list = ckpt["train_loss_list"]
|
| 27 |
-
val_step_list = ckpt["val_step_list"]
|
| 28 |
-
val_loss_list = ckpt["val_loss_list"]
|
| 29 |
-
self.loss_list.append((line_name, train_step_list, train_loss_list, val_step_list, val_loss_list))
|
| 30 |
-
|
| 31 |
-
def draw(self, save_path, plot_val=True):
|
| 32 |
-
# Global settings
|
| 33 |
-
plt.rcParams["font.size"] = 14
|
| 34 |
-
plt.rcParams["font.family"] = "serif"
|
| 35 |
-
plt.rcParams["font.sans-serif"] = ["Arial", "DejaVu Sans", "Lucida Grande"]
|
| 36 |
-
plt.rcParams["font.serif"] = ["Times New Roman", "DejaVu Serif"]
|
| 37 |
-
|
| 38 |
-
# Creating the plot
|
| 39 |
-
plt.figure(figsize=(7.766, 4.8)) # Golden ratio
|
| 40 |
-
for loss in self.loss_list:
|
| 41 |
-
if plot_val:
|
| 42 |
-
(line,) = plt.plot(loss[1], loss[2], label=loss[0], linewidth=0.5, alpha=0.5)
|
| 43 |
-
line_color = line.get_color()
|
| 44 |
-
plt.plot(loss[3], loss[4], linewidth=1.5, color=line_color)
|
| 45 |
-
else:
|
| 46 |
-
plt.plot(loss[1], loss[2], label=loss[0], linewidth=1)
|
| 47 |
-
plt.xlabel("Step")
|
| 48 |
-
plt.ylabel("Loss")
|
| 49 |
-
legend = plt.legend()
|
| 50 |
-
# legend = plt.legend(loc='upper right', bbox_to_anchor=(1, 0.82))
|
| 51 |
-
|
| 52 |
-
# Adjust the linewidth of legend
|
| 53 |
-
for line in legend.get_lines():
|
| 54 |
-
line.set_linewidth(2)
|
| 55 |
-
|
| 56 |
-
plt.savefig(save_path, transparent=True)
|
| 57 |
-
plt.close()
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
if __name__ == "__main__":
|
| 61 |
-
chart = Chart()
|
| 62 |
-
chart.add_ckpt("output/syncnet/train-2024_10_28-23:16:40/checkpoints/checkpoint-20000.pt", "Wav2Lip SyncNet")
|
| 63 |
-
chart.add_ckpt("output/syncnet/train-2024_10_29-20:13:43/checkpoints/checkpoint-20000.pt", "StableSyncNet")
|
| 64 |
-
chart.draw("ablation.pdf", plot_val=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/eval_fvd.py
DELETED
|
@@ -1,98 +0,0 @@
|
|
| 1 |
-
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import mediapipe as mp
|
| 16 |
-
import cv2
|
| 17 |
-
from decord import VideoReader
|
| 18 |
-
import os
|
| 19 |
-
import numpy as np
|
| 20 |
-
import torch
|
| 21 |
-
import tqdm
|
| 22 |
-
from eval.fvd import compute_our_fvd
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
class FVD:
|
| 26 |
-
def __init__(self, resolution=(224, 224)):
|
| 27 |
-
self.face_detector = mp.solutions.face_detection.FaceDetection(model_selection=0, min_detection_confidence=0.5)
|
| 28 |
-
self.resolution = resolution
|
| 29 |
-
|
| 30 |
-
def detect_face(self, image):
|
| 31 |
-
height, width = image.shape[:2]
|
| 32 |
-
# Process the image and detect faces.
|
| 33 |
-
results = self.face_detector.process(image)
|
| 34 |
-
|
| 35 |
-
if not results.detections: # Face not detected
|
| 36 |
-
raise RuntimeError("Face not detected")
|
| 37 |
-
|
| 38 |
-
detection = results.detections[0] # Only use the first face in the image
|
| 39 |
-
bounding_box = detection.location_data.relative_bounding_box
|
| 40 |
-
xmin = int(bounding_box.xmin * width)
|
| 41 |
-
ymin = int(bounding_box.ymin * height)
|
| 42 |
-
face_width = int(bounding_box.width * width)
|
| 43 |
-
face_height = int(bounding_box.height * height)
|
| 44 |
-
|
| 45 |
-
# Crop the image to the bounding box.
|
| 46 |
-
xmin = max(0, xmin)
|
| 47 |
-
ymin = max(0, ymin)
|
| 48 |
-
xmax = min(width, xmin + face_width)
|
| 49 |
-
ymax = min(height, ymin + face_height)
|
| 50 |
-
image = image[ymin:ymax, xmin:xmax]
|
| 51 |
-
|
| 52 |
-
return image
|
| 53 |
-
|
| 54 |
-
def detect_video(self, video_path):
|
| 55 |
-
vr = VideoReader(video_path)
|
| 56 |
-
video_frames = vr[20:36].asnumpy()
|
| 57 |
-
vr.seek(0) # avoid memory leak
|
| 58 |
-
faces = []
|
| 59 |
-
for frame in video_frames:
|
| 60 |
-
face = self.detect_face(frame)
|
| 61 |
-
face = cv2.resize(face, (self.resolution[1], self.resolution[0]), interpolation=cv2.INTER_AREA)
|
| 62 |
-
faces.append(face)
|
| 63 |
-
|
| 64 |
-
if len(faces) != 16:
|
| 65 |
-
return RuntimeError("Insufficient consecutive frames of faces (less than 16).")
|
| 66 |
-
faces = np.stack(faces, axis=0) # (f, h, w, c)
|
| 67 |
-
faces = torch.from_numpy(faces)
|
| 68 |
-
return faces
|
| 69 |
-
|
| 70 |
-
def detect_videos(self, videos_dir: str):
|
| 71 |
-
videos_list = []
|
| 72 |
-
|
| 73 |
-
if videos_dir.endswith(".mp4"):
|
| 74 |
-
video_faces = self.detect_video(videos_dir)
|
| 75 |
-
videos_list.append(video_faces)
|
| 76 |
-
else:
|
| 77 |
-
for file in tqdm.tqdm(os.listdir(videos_dir)):
|
| 78 |
-
if file.endswith(".mp4"):
|
| 79 |
-
video_path = os.path.join(videos_dir, file)
|
| 80 |
-
video_faces = self.detect_video(video_path)
|
| 81 |
-
videos_list.append(video_faces)
|
| 82 |
-
|
| 83 |
-
videos_list = torch.stack(videos_list) / 255.0
|
| 84 |
-
return videos_list
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
def eval_fvd(real_videos_dir: str, fake_videos_dir: str):
|
| 88 |
-
fvd = FVD()
|
| 89 |
-
real_videos = fvd.detect_videos(real_videos_dir)
|
| 90 |
-
fake_videos = fvd.detect_videos(fake_videos_dir)
|
| 91 |
-
fvd_value = compute_our_fvd(real_videos, fake_videos, device="cpu")
|
| 92 |
-
print(f"FVD: {fvd_value:.3f}")
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
if __name__ == "__main__":
|
| 96 |
-
real_videos_dir = "dir1"
|
| 97 |
-
fake_videos_dir = "dir2"
|
| 98 |
-
eval_fvd(real_videos_dir, fake_videos_dir)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/eval_sync_conf.py
DELETED
|
@@ -1,77 +0,0 @@
|
|
| 1 |
-
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import argparse
|
| 16 |
-
import os
|
| 17 |
-
import tqdm
|
| 18 |
-
from statistics import fmean
|
| 19 |
-
from eval.syncnet import SyncNetEval
|
| 20 |
-
from eval.syncnet_detect import SyncNetDetector
|
| 21 |
-
from latentsync.utils.util import red_text
|
| 22 |
-
import torch
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
def syncnet_eval(syncnet, syncnet_detector, video_path, temp_dir, detect_results_dir="detect_results"):
|
| 26 |
-
syncnet_detector(video_path=video_path, min_track=50)
|
| 27 |
-
crop_videos = os.listdir(os.path.join(detect_results_dir, "crop"))
|
| 28 |
-
if crop_videos == []:
|
| 29 |
-
raise Exception(red_text(f"Face not detected in {video_path}"))
|
| 30 |
-
av_offset_list = []
|
| 31 |
-
conf_list = []
|
| 32 |
-
for video in crop_videos:
|
| 33 |
-
av_offset, _, conf = syncnet.evaluate(
|
| 34 |
-
video_path=os.path.join(detect_results_dir, "crop", video), temp_dir=temp_dir
|
| 35 |
-
)
|
| 36 |
-
av_offset_list.append(av_offset)
|
| 37 |
-
conf_list.append(conf)
|
| 38 |
-
av_offset = int(fmean(av_offset_list))
|
| 39 |
-
conf = fmean(conf_list)
|
| 40 |
-
print(f"Input video: {video_path}\nSyncNet confidence: {conf:.2f}\nAV offset: {av_offset}")
|
| 41 |
-
return av_offset, conf
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
def main():
|
| 45 |
-
parser = argparse.ArgumentParser(description="SyncNet")
|
| 46 |
-
parser.add_argument("--initial_model", type=str, default="checkpoints/auxiliary/syncnet_v2.model", help="")
|
| 47 |
-
parser.add_argument("--video_path", type=str, default=None, help="")
|
| 48 |
-
parser.add_argument("--videos_dir", type=str, default="/root/processed")
|
| 49 |
-
parser.add_argument("--temp_dir", type=str, default="temp", help="")
|
| 50 |
-
|
| 51 |
-
args = parser.parse_args()
|
| 52 |
-
|
| 53 |
-
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 54 |
-
|
| 55 |
-
syncnet = SyncNetEval(device=device)
|
| 56 |
-
syncnet.loadParameters(args.initial_model)
|
| 57 |
-
|
| 58 |
-
syncnet_detector = SyncNetDetector(device=device, detect_results_dir="detect_results")
|
| 59 |
-
|
| 60 |
-
if args.video_path is not None:
|
| 61 |
-
syncnet_eval(syncnet, syncnet_detector, args.video_path, args.temp_dir)
|
| 62 |
-
else:
|
| 63 |
-
sync_conf_list = []
|
| 64 |
-
video_names = sorted([f for f in os.listdir(args.videos_dir) if f.endswith(".mp4")])
|
| 65 |
-
for video_name in tqdm.tqdm(video_names):
|
| 66 |
-
try:
|
| 67 |
-
_, conf = syncnet_eval(
|
| 68 |
-
syncnet, syncnet_detector, os.path.join(args.videos_dir, video_name), args.temp_dir
|
| 69 |
-
)
|
| 70 |
-
sync_conf_list.append(conf)
|
| 71 |
-
except Exception as e:
|
| 72 |
-
print(e)
|
| 73 |
-
print(f"The average sync confidence is {fmean(sync_conf_list):.02f}")
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
if __name__ == "__main__":
|
| 77 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/eval_sync_conf.sh
DELETED
|
@@ -1,2 +0,0 @@
|
|
| 1 |
-
#!/bin/bash
|
| 2 |
-
python -m eval.eval_sync_conf --video_path "video_out.mp4"
|
|
|
|
|
|
|
|
|
eval/eval_syncnet_acc.py
DELETED
|
@@ -1,137 +0,0 @@
|
|
| 1 |
-
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import argparse
|
| 16 |
-
import os
|
| 17 |
-
import sys
|
| 18 |
-
from tqdm.auto import tqdm
|
| 19 |
-
import torch
|
| 20 |
-
import torch.nn as nn
|
| 21 |
-
from einops import rearrange
|
| 22 |
-
from latentsync.models.stable_syncnet import StableSyncNet
|
| 23 |
-
from latentsync.data.syncnet_dataset import SyncNetDataset
|
| 24 |
-
from diffusers import AutoencoderKL
|
| 25 |
-
from omegaconf import OmegaConf
|
| 26 |
-
from accelerate.utils import set_seed
|
| 27 |
-
|
| 28 |
-
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 29 |
-
from config import MODELS_DIR
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
def main(config):
|
| 33 |
-
set_seed(config.run.seed)
|
| 34 |
-
|
| 35 |
-
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 36 |
-
|
| 37 |
-
if config.data.latent_space:
|
| 38 |
-
vae = AutoencoderKL.from_pretrained(
|
| 39 |
-
"runwayml/stable-diffusion-inpainting",
|
| 40 |
-
subfolder="vae",
|
| 41 |
-
revision="fp16",
|
| 42 |
-
torch_dtype=torch.float16,
|
| 43 |
-
cache_dir=MODELS_DIR,
|
| 44 |
-
)
|
| 45 |
-
vae.requires_grad_(False)
|
| 46 |
-
vae.to(device)
|
| 47 |
-
|
| 48 |
-
# Dataset and Dataloader setup
|
| 49 |
-
dataset = SyncNetDataset(
|
| 50 |
-
config.data.val_data_dir, config.data.val_fileslist, config
|
| 51 |
-
)
|
| 52 |
-
|
| 53 |
-
test_dataloader = torch.utils.data.DataLoader(
|
| 54 |
-
dataset,
|
| 55 |
-
batch_size=config.data.batch_size,
|
| 56 |
-
shuffle=False,
|
| 57 |
-
num_workers=config.data.num_workers,
|
| 58 |
-
drop_last=False,
|
| 59 |
-
worker_init_fn=dataset.worker_init_fn,
|
| 60 |
-
)
|
| 61 |
-
|
| 62 |
-
# Model
|
| 63 |
-
syncnet = StableSyncNet(OmegaConf.to_container(config.model)).to(device)
|
| 64 |
-
|
| 65 |
-
print(f"Load checkpoint from: {config.ckpt.inference_ckpt_path}")
|
| 66 |
-
checkpoint = torch.load(
|
| 67 |
-
config.ckpt.inference_ckpt_path, map_location=device, weights_only=True
|
| 68 |
-
)
|
| 69 |
-
|
| 70 |
-
syncnet.load_state_dict(checkpoint["state_dict"])
|
| 71 |
-
syncnet.to(dtype=torch.float16)
|
| 72 |
-
syncnet.requires_grad_(False)
|
| 73 |
-
syncnet.eval()
|
| 74 |
-
|
| 75 |
-
global_step = 0
|
| 76 |
-
num_val_batches = config.data.num_val_samples // config.data.batch_size
|
| 77 |
-
progress_bar = tqdm(range(0, num_val_batches), initial=0, desc="Testing accuracy")
|
| 78 |
-
|
| 79 |
-
num_correct_preds = 0
|
| 80 |
-
num_total_preds = 0
|
| 81 |
-
|
| 82 |
-
while True:
|
| 83 |
-
for step, batch in enumerate(test_dataloader):
|
| 84 |
-
### >>>> Test >>>> ###
|
| 85 |
-
|
| 86 |
-
frames = batch["frames"].to(device, dtype=torch.float16)
|
| 87 |
-
audio_samples = batch["audio_samples"].to(device, dtype=torch.float16)
|
| 88 |
-
y = batch["y"].to(device, dtype=torch.float16).squeeze(1)
|
| 89 |
-
|
| 90 |
-
if config.data.latent_space:
|
| 91 |
-
frames = rearrange(frames, "b f c h w -> (b f) c h w")
|
| 92 |
-
|
| 93 |
-
with torch.no_grad():
|
| 94 |
-
frames = vae.encode(frames).latent_dist.sample() * 0.18215
|
| 95 |
-
|
| 96 |
-
frames = rearrange(
|
| 97 |
-
frames, "(b f) c h w -> b (f c) h w", f=config.data.num_frames
|
| 98 |
-
)
|
| 99 |
-
else:
|
| 100 |
-
frames = rearrange(frames, "b f c h w -> b (f c) h w")
|
| 101 |
-
|
| 102 |
-
if config.data.lower_half:
|
| 103 |
-
height = frames.shape[2]
|
| 104 |
-
frames = frames[:, :, height // 2 :, :]
|
| 105 |
-
|
| 106 |
-
with torch.no_grad():
|
| 107 |
-
vision_embeds, audio_embeds = syncnet(frames, audio_samples)
|
| 108 |
-
|
| 109 |
-
sims = nn.functional.cosine_similarity(vision_embeds, audio_embeds)
|
| 110 |
-
|
| 111 |
-
preds = (sims > 0.5).to(dtype=torch.float16)
|
| 112 |
-
num_correct_preds += (preds == y).sum().item()
|
| 113 |
-
num_total_preds += len(sims)
|
| 114 |
-
|
| 115 |
-
progress_bar.update(1)
|
| 116 |
-
global_step += 1
|
| 117 |
-
|
| 118 |
-
if global_step >= num_val_batches:
|
| 119 |
-
progress_bar.close()
|
| 120 |
-
print(
|
| 121 |
-
f"SyncNet Accuracy: {num_correct_preds / num_total_preds * 100:.2f}%"
|
| 122 |
-
)
|
| 123 |
-
return
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
if __name__ == "__main__":
|
| 127 |
-
parser = argparse.ArgumentParser(description="Code to test the accuracy of SyncNet")
|
| 128 |
-
|
| 129 |
-
parser.add_argument(
|
| 130 |
-
"--config_path", type=str, default="configs/syncnet/syncnet_16_latent.yaml"
|
| 131 |
-
)
|
| 132 |
-
args = parser.parse_args()
|
| 133 |
-
|
| 134 |
-
# Load a configuration file
|
| 135 |
-
config = OmegaConf.load(args.config_path)
|
| 136 |
-
|
| 137 |
-
main(config)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/eval_syncnet_acc.sh
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
#!/bin/bash
|
| 2 |
-
|
| 3 |
-
python -m eval.eval_syncnet_acc --config_path "configs/syncnet/syncnet_16_pixel_attn.yaml"
|
|
|
|
|
|
|
|
|
|
|
|
eval/fvd.py
DELETED
|
@@ -1,58 +0,0 @@
|
|
| 1 |
-
# Adapted from https://github.com/universome/fvd-comparison/blob/master/our_fvd.py
|
| 2 |
-
|
| 3 |
-
from typing import Tuple
|
| 4 |
-
import scipy
|
| 5 |
-
import numpy as np
|
| 6 |
-
import torch
|
| 7 |
-
from latentsync.utils.util import check_model_and_download
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
def compute_fvd(feats_fake: np.ndarray, feats_real: np.ndarray) -> float:
|
| 11 |
-
mu_gen, sigma_gen = compute_stats(feats_fake)
|
| 12 |
-
mu_real, sigma_real = compute_stats(feats_real)
|
| 13 |
-
|
| 14 |
-
m = np.square(mu_gen - mu_real).sum()
|
| 15 |
-
s, _ = scipy.linalg.sqrtm(np.dot(sigma_gen, sigma_real), disp=False) # pylint: disable=no-member
|
| 16 |
-
fid = np.real(m + np.trace(sigma_gen + sigma_real - s * 2))
|
| 17 |
-
|
| 18 |
-
return float(fid)
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
def compute_stats(feats: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
| 22 |
-
mu = feats.mean(axis=0) # [d]
|
| 23 |
-
sigma = np.cov(feats, rowvar=False) # [d, d]
|
| 24 |
-
|
| 25 |
-
return mu, sigma
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
@torch.no_grad()
|
| 29 |
-
def compute_our_fvd(videos_fake: np.ndarray, videos_real: np.ndarray, device: str = "cuda") -> float:
|
| 30 |
-
i3d_path = "checkpoints/auxiliary/i3d_torchscript.pt"
|
| 31 |
-
check_model_and_download(i3d_path)
|
| 32 |
-
i3d_kwargs = dict(
|
| 33 |
-
rescale=False, resize=False, return_features=True
|
| 34 |
-
) # Return raw features before the softmax layer.
|
| 35 |
-
|
| 36 |
-
with open(i3d_path, "rb") as f:
|
| 37 |
-
i3d_model = torch.jit.load(f).eval().to(device)
|
| 38 |
-
|
| 39 |
-
videos_fake = videos_fake.permute(0, 4, 1, 2, 3).to(device)
|
| 40 |
-
videos_real = videos_real.permute(0, 4, 1, 2, 3).to(device)
|
| 41 |
-
|
| 42 |
-
feats_fake = i3d_model(videos_fake, **i3d_kwargs).cpu().numpy()
|
| 43 |
-
feats_real = i3d_model(videos_real, **i3d_kwargs).cpu().numpy()
|
| 44 |
-
|
| 45 |
-
return compute_fvd(feats_fake, feats_real)
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
def main():
|
| 49 |
-
# input shape: (b, f, h, w, c)
|
| 50 |
-
videos_fake = torch.rand(10, 16, 224, 224, 3)
|
| 51 |
-
videos_real = torch.rand(10, 16, 224, 224, 3)
|
| 52 |
-
|
| 53 |
-
our_fvd_result = compute_our_fvd(videos_fake, videos_real)
|
| 54 |
-
print(f"[FVD scores] Ours: {our_fvd_result}")
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
if __name__ == "__main__":
|
| 58 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/hyper_iqa.py
DELETED
|
@@ -1,343 +0,0 @@
|
|
| 1 |
-
# Adapted from https://github.com/SSL92/hyperIQA/blob/master/models.py
|
| 2 |
-
|
| 3 |
-
import torch as torch
|
| 4 |
-
import torch.nn as nn
|
| 5 |
-
from torch.nn import functional as F
|
| 6 |
-
from torch.nn import init
|
| 7 |
-
import math
|
| 8 |
-
import torch.utils.model_zoo as model_zoo
|
| 9 |
-
|
| 10 |
-
model_urls = {
|
| 11 |
-
'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
|
| 12 |
-
'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
|
| 13 |
-
'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
|
| 14 |
-
'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
|
| 15 |
-
'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
class HyperNet(nn.Module):
|
| 20 |
-
"""
|
| 21 |
-
Hyper network for learning perceptual rules.
|
| 22 |
-
|
| 23 |
-
Args:
|
| 24 |
-
lda_out_channels: local distortion aware module output size.
|
| 25 |
-
hyper_in_channels: input feature channels for hyper network.
|
| 26 |
-
target_in_size: input vector size for target network.
|
| 27 |
-
target_fc(i)_size: fully connection layer size of target network.
|
| 28 |
-
feature_size: input feature map width/height for hyper network.
|
| 29 |
-
|
| 30 |
-
Note:
|
| 31 |
-
For size match, input args must satisfy: 'target_fc(i)_size * target_fc(i+1)_size' is divisible by 'feature_size ^ 2'.
|
| 32 |
-
|
| 33 |
-
"""
|
| 34 |
-
def __init__(self, lda_out_channels, hyper_in_channels, target_in_size, target_fc1_size, target_fc2_size, target_fc3_size, target_fc4_size, feature_size):
|
| 35 |
-
super(HyperNet, self).__init__()
|
| 36 |
-
|
| 37 |
-
self.hyperInChn = hyper_in_channels
|
| 38 |
-
self.target_in_size = target_in_size
|
| 39 |
-
self.f1 = target_fc1_size
|
| 40 |
-
self.f2 = target_fc2_size
|
| 41 |
-
self.f3 = target_fc3_size
|
| 42 |
-
self.f4 = target_fc4_size
|
| 43 |
-
self.feature_size = feature_size
|
| 44 |
-
|
| 45 |
-
self.res = resnet50_backbone(lda_out_channels, target_in_size, pretrained=True)
|
| 46 |
-
|
| 47 |
-
self.pool = nn.AdaptiveAvgPool2d((1, 1))
|
| 48 |
-
|
| 49 |
-
# Conv layers for resnet output features
|
| 50 |
-
self.conv1 = nn.Sequential(
|
| 51 |
-
nn.Conv2d(2048, 1024, 1, padding=(0, 0)),
|
| 52 |
-
nn.ReLU(inplace=True),
|
| 53 |
-
nn.Conv2d(1024, 512, 1, padding=(0, 0)),
|
| 54 |
-
nn.ReLU(inplace=True),
|
| 55 |
-
nn.Conv2d(512, self.hyperInChn, 1, padding=(0, 0)),
|
| 56 |
-
nn.ReLU(inplace=True)
|
| 57 |
-
)
|
| 58 |
-
|
| 59 |
-
# Hyper network part, conv for generating target fc weights, fc for generating target fc biases
|
| 60 |
-
self.fc1w_conv = nn.Conv2d(self.hyperInChn, int(self.target_in_size * self.f1 / feature_size ** 2), 3, padding=(1, 1))
|
| 61 |
-
self.fc1b_fc = nn.Linear(self.hyperInChn, self.f1)
|
| 62 |
-
|
| 63 |
-
self.fc2w_conv = nn.Conv2d(self.hyperInChn, int(self.f1 * self.f2 / feature_size ** 2), 3, padding=(1, 1))
|
| 64 |
-
self.fc2b_fc = nn.Linear(self.hyperInChn, self.f2)
|
| 65 |
-
|
| 66 |
-
self.fc3w_conv = nn.Conv2d(self.hyperInChn, int(self.f2 * self.f3 / feature_size ** 2), 3, padding=(1, 1))
|
| 67 |
-
self.fc3b_fc = nn.Linear(self.hyperInChn, self.f3)
|
| 68 |
-
|
| 69 |
-
self.fc4w_conv = nn.Conv2d(self.hyperInChn, int(self.f3 * self.f4 / feature_size ** 2), 3, padding=(1, 1))
|
| 70 |
-
self.fc4b_fc = nn.Linear(self.hyperInChn, self.f4)
|
| 71 |
-
|
| 72 |
-
self.fc5w_fc = nn.Linear(self.hyperInChn, self.f4)
|
| 73 |
-
self.fc5b_fc = nn.Linear(self.hyperInChn, 1)
|
| 74 |
-
|
| 75 |
-
# initialize
|
| 76 |
-
for i, m_name in enumerate(self._modules):
|
| 77 |
-
if i > 2:
|
| 78 |
-
nn.init.kaiming_normal_(self._modules[m_name].weight.data)
|
| 79 |
-
|
| 80 |
-
def forward(self, img):
|
| 81 |
-
feature_size = self.feature_size
|
| 82 |
-
|
| 83 |
-
res_out = self.res(img)
|
| 84 |
-
|
| 85 |
-
# input vector for target net
|
| 86 |
-
target_in_vec = res_out['target_in_vec'].reshape(-1, self.target_in_size, 1, 1)
|
| 87 |
-
|
| 88 |
-
# input features for hyper net
|
| 89 |
-
hyper_in_feat = self.conv1(res_out['hyper_in_feat']).reshape(-1, self.hyperInChn, feature_size, feature_size)
|
| 90 |
-
|
| 91 |
-
# generating target net weights & biases
|
| 92 |
-
target_fc1w = self.fc1w_conv(hyper_in_feat).reshape(-1, self.f1, self.target_in_size, 1, 1)
|
| 93 |
-
target_fc1b = self.fc1b_fc(self.pool(hyper_in_feat).squeeze()).reshape(-1, self.f1)
|
| 94 |
-
|
| 95 |
-
target_fc2w = self.fc2w_conv(hyper_in_feat).reshape(-1, self.f2, self.f1, 1, 1)
|
| 96 |
-
target_fc2b = self.fc2b_fc(self.pool(hyper_in_feat).squeeze()).reshape(-1, self.f2)
|
| 97 |
-
|
| 98 |
-
target_fc3w = self.fc3w_conv(hyper_in_feat).reshape(-1, self.f3, self.f2, 1, 1)
|
| 99 |
-
target_fc3b = self.fc3b_fc(self.pool(hyper_in_feat).squeeze()).reshape(-1, self.f3)
|
| 100 |
-
|
| 101 |
-
target_fc4w = self.fc4w_conv(hyper_in_feat).reshape(-1, self.f4, self.f3, 1, 1)
|
| 102 |
-
target_fc4b = self.fc4b_fc(self.pool(hyper_in_feat).squeeze()).reshape(-1, self.f4)
|
| 103 |
-
|
| 104 |
-
target_fc5w = self.fc5w_fc(self.pool(hyper_in_feat).squeeze()).reshape(-1, 1, self.f4, 1, 1)
|
| 105 |
-
target_fc5b = self.fc5b_fc(self.pool(hyper_in_feat).squeeze()).reshape(-1, 1)
|
| 106 |
-
|
| 107 |
-
out = {}
|
| 108 |
-
out['target_in_vec'] = target_in_vec
|
| 109 |
-
out['target_fc1w'] = target_fc1w
|
| 110 |
-
out['target_fc1b'] = target_fc1b
|
| 111 |
-
out['target_fc2w'] = target_fc2w
|
| 112 |
-
out['target_fc2b'] = target_fc2b
|
| 113 |
-
out['target_fc3w'] = target_fc3w
|
| 114 |
-
out['target_fc3b'] = target_fc3b
|
| 115 |
-
out['target_fc4w'] = target_fc4w
|
| 116 |
-
out['target_fc4b'] = target_fc4b
|
| 117 |
-
out['target_fc5w'] = target_fc5w
|
| 118 |
-
out['target_fc5b'] = target_fc5b
|
| 119 |
-
|
| 120 |
-
return out
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
class TargetNet(nn.Module):
|
| 124 |
-
"""
|
| 125 |
-
Target network for quality prediction.
|
| 126 |
-
"""
|
| 127 |
-
def __init__(self, paras):
|
| 128 |
-
super(TargetNet, self).__init__()
|
| 129 |
-
self.l1 = nn.Sequential(
|
| 130 |
-
TargetFC(paras['target_fc1w'], paras['target_fc1b']),
|
| 131 |
-
nn.Sigmoid(),
|
| 132 |
-
)
|
| 133 |
-
self.l2 = nn.Sequential(
|
| 134 |
-
TargetFC(paras['target_fc2w'], paras['target_fc2b']),
|
| 135 |
-
nn.Sigmoid(),
|
| 136 |
-
)
|
| 137 |
-
|
| 138 |
-
self.l3 = nn.Sequential(
|
| 139 |
-
TargetFC(paras['target_fc3w'], paras['target_fc3b']),
|
| 140 |
-
nn.Sigmoid(),
|
| 141 |
-
)
|
| 142 |
-
|
| 143 |
-
self.l4 = nn.Sequential(
|
| 144 |
-
TargetFC(paras['target_fc4w'], paras['target_fc4b']),
|
| 145 |
-
nn.Sigmoid(),
|
| 146 |
-
TargetFC(paras['target_fc5w'], paras['target_fc5b']),
|
| 147 |
-
)
|
| 148 |
-
|
| 149 |
-
def forward(self, x):
|
| 150 |
-
q = self.l1(x)
|
| 151 |
-
# q = F.dropout(q)
|
| 152 |
-
q = self.l2(q)
|
| 153 |
-
q = self.l3(q)
|
| 154 |
-
q = self.l4(q).squeeze()
|
| 155 |
-
return q
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
class TargetFC(nn.Module):
|
| 159 |
-
"""
|
| 160 |
-
Fully connection operations for target net
|
| 161 |
-
|
| 162 |
-
Note:
|
| 163 |
-
Weights & biases are different for different images in a batch,
|
| 164 |
-
thus here we use group convolution for calculating images in a batch with individual weights & biases.
|
| 165 |
-
"""
|
| 166 |
-
def __init__(self, weight, bias):
|
| 167 |
-
super(TargetFC, self).__init__()
|
| 168 |
-
self.weight = weight
|
| 169 |
-
self.bias = bias
|
| 170 |
-
|
| 171 |
-
def forward(self, input_):
|
| 172 |
-
|
| 173 |
-
input_re = input_.reshape(-1, input_.shape[0] * input_.shape[1], input_.shape[2], input_.shape[3])
|
| 174 |
-
weight_re = self.weight.reshape(self.weight.shape[0] * self.weight.shape[1], self.weight.shape[2], self.weight.shape[3], self.weight.shape[4])
|
| 175 |
-
bias_re = self.bias.reshape(self.bias.shape[0] * self.bias.shape[1])
|
| 176 |
-
out = F.conv2d(input=input_re, weight=weight_re, bias=bias_re, groups=self.weight.shape[0])
|
| 177 |
-
|
| 178 |
-
return out.reshape(input_.shape[0], self.weight.shape[1], input_.shape[2], input_.shape[3])
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
class Bottleneck(nn.Module):
|
| 182 |
-
expansion = 4
|
| 183 |
-
|
| 184 |
-
def __init__(self, inplanes, planes, stride=1, downsample=None):
|
| 185 |
-
super(Bottleneck, self).__init__()
|
| 186 |
-
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
|
| 187 |
-
self.bn1 = nn.BatchNorm2d(planes)
|
| 188 |
-
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
|
| 189 |
-
padding=1, bias=False)
|
| 190 |
-
self.bn2 = nn.BatchNorm2d(planes)
|
| 191 |
-
self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
|
| 192 |
-
self.bn3 = nn.BatchNorm2d(planes * 4)
|
| 193 |
-
self.relu = nn.ReLU(inplace=True)
|
| 194 |
-
self.downsample = downsample
|
| 195 |
-
self.stride = stride
|
| 196 |
-
|
| 197 |
-
def forward(self, x):
|
| 198 |
-
residual = x
|
| 199 |
-
|
| 200 |
-
out = self.conv1(x)
|
| 201 |
-
out = self.bn1(out)
|
| 202 |
-
out = self.relu(out)
|
| 203 |
-
|
| 204 |
-
out = self.conv2(out)
|
| 205 |
-
out = self.bn2(out)
|
| 206 |
-
out = self.relu(out)
|
| 207 |
-
|
| 208 |
-
out = self.conv3(out)
|
| 209 |
-
out = self.bn3(out)
|
| 210 |
-
|
| 211 |
-
if self.downsample is not None:
|
| 212 |
-
residual = self.downsample(x)
|
| 213 |
-
|
| 214 |
-
out += residual
|
| 215 |
-
out = self.relu(out)
|
| 216 |
-
|
| 217 |
-
return out
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
class ResNetBackbone(nn.Module):
|
| 221 |
-
|
| 222 |
-
def __init__(self, lda_out_channels, in_chn, block, layers, num_classes=1000):
|
| 223 |
-
super(ResNetBackbone, self).__init__()
|
| 224 |
-
self.inplanes = 64
|
| 225 |
-
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
|
| 226 |
-
self.bn1 = nn.BatchNorm2d(64)
|
| 227 |
-
self.relu = nn.ReLU(inplace=True)
|
| 228 |
-
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
| 229 |
-
self.layer1 = self._make_layer(block, 64, layers[0])
|
| 230 |
-
self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
|
| 231 |
-
self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
|
| 232 |
-
self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
|
| 233 |
-
|
| 234 |
-
# local distortion aware module
|
| 235 |
-
self.lda1_pool = nn.Sequential(
|
| 236 |
-
nn.Conv2d(256, 16, kernel_size=1, stride=1, padding=0, bias=False),
|
| 237 |
-
nn.AvgPool2d(7, stride=7),
|
| 238 |
-
)
|
| 239 |
-
self.lda1_fc = nn.Linear(16 * 64, lda_out_channels)
|
| 240 |
-
|
| 241 |
-
self.lda2_pool = nn.Sequential(
|
| 242 |
-
nn.Conv2d(512, 32, kernel_size=1, stride=1, padding=0, bias=False),
|
| 243 |
-
nn.AvgPool2d(7, stride=7),
|
| 244 |
-
)
|
| 245 |
-
self.lda2_fc = nn.Linear(32 * 16, lda_out_channels)
|
| 246 |
-
|
| 247 |
-
self.lda3_pool = nn.Sequential(
|
| 248 |
-
nn.Conv2d(1024, 64, kernel_size=1, stride=1, padding=0, bias=False),
|
| 249 |
-
nn.AvgPool2d(7, stride=7),
|
| 250 |
-
)
|
| 251 |
-
self.lda3_fc = nn.Linear(64 * 4, lda_out_channels)
|
| 252 |
-
|
| 253 |
-
self.lda4_pool = nn.AvgPool2d(7, stride=7)
|
| 254 |
-
self.lda4_fc = nn.Linear(2048, in_chn - lda_out_channels * 3)
|
| 255 |
-
|
| 256 |
-
for m in self.modules():
|
| 257 |
-
if isinstance(m, nn.Conv2d):
|
| 258 |
-
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
|
| 259 |
-
m.weight.data.normal_(0, math.sqrt(2. / n))
|
| 260 |
-
elif isinstance(m, nn.BatchNorm2d):
|
| 261 |
-
m.weight.data.fill_(1)
|
| 262 |
-
m.bias.data.zero_()
|
| 263 |
-
|
| 264 |
-
# initialize
|
| 265 |
-
nn.init.kaiming_normal_(self.lda1_pool._modules['0'].weight.data)
|
| 266 |
-
nn.init.kaiming_normal_(self.lda2_pool._modules['0'].weight.data)
|
| 267 |
-
nn.init.kaiming_normal_(self.lda3_pool._modules['0'].weight.data)
|
| 268 |
-
nn.init.kaiming_normal_(self.lda1_fc.weight.data)
|
| 269 |
-
nn.init.kaiming_normal_(self.lda2_fc.weight.data)
|
| 270 |
-
nn.init.kaiming_normal_(self.lda3_fc.weight.data)
|
| 271 |
-
nn.init.kaiming_normal_(self.lda4_fc.weight.data)
|
| 272 |
-
|
| 273 |
-
def _make_layer(self, block, planes, blocks, stride=1):
|
| 274 |
-
downsample = None
|
| 275 |
-
if stride != 1 or self.inplanes != planes * block.expansion:
|
| 276 |
-
downsample = nn.Sequential(
|
| 277 |
-
nn.Conv2d(self.inplanes, planes * block.expansion,
|
| 278 |
-
kernel_size=1, stride=stride, bias=False),
|
| 279 |
-
nn.BatchNorm2d(planes * block.expansion),
|
| 280 |
-
)
|
| 281 |
-
|
| 282 |
-
layers = []
|
| 283 |
-
layers.append(block(self.inplanes, planes, stride, downsample))
|
| 284 |
-
self.inplanes = planes * block.expansion
|
| 285 |
-
for i in range(1, blocks):
|
| 286 |
-
layers.append(block(self.inplanes, planes))
|
| 287 |
-
|
| 288 |
-
return nn.Sequential(*layers)
|
| 289 |
-
|
| 290 |
-
def forward(self, x):
|
| 291 |
-
x = self.conv1(x)
|
| 292 |
-
x = self.bn1(x)
|
| 293 |
-
x = self.relu(x)
|
| 294 |
-
x = self.maxpool(x)
|
| 295 |
-
x = self.layer1(x)
|
| 296 |
-
|
| 297 |
-
# the same effect as lda operation in the paper, but save much more memory
|
| 298 |
-
lda_1 = self.lda1_fc(self.lda1_pool(x).reshape(x.size(0), -1))
|
| 299 |
-
x = self.layer2(x)
|
| 300 |
-
lda_2 = self.lda2_fc(self.lda2_pool(x).reshape(x.size(0), -1))
|
| 301 |
-
x = self.layer3(x)
|
| 302 |
-
lda_3 = self.lda3_fc(self.lda3_pool(x).reshape(x.size(0), -1))
|
| 303 |
-
x = self.layer4(x)
|
| 304 |
-
lda_4 = self.lda4_fc(self.lda4_pool(x).reshape(x.size(0), -1))
|
| 305 |
-
|
| 306 |
-
vec = torch.cat((lda_1, lda_2, lda_3, lda_4), 1)
|
| 307 |
-
|
| 308 |
-
out = {}
|
| 309 |
-
out['hyper_in_feat'] = x
|
| 310 |
-
out['target_in_vec'] = vec
|
| 311 |
-
|
| 312 |
-
return out
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
def resnet50_backbone(lda_out_channels, in_chn, pretrained=False, **kwargs):
|
| 316 |
-
"""Constructs a ResNet-50 model_hyper.
|
| 317 |
-
|
| 318 |
-
Args:
|
| 319 |
-
pretrained (bool): If True, returns a model_hyper pre-trained on ImageNet
|
| 320 |
-
"""
|
| 321 |
-
model = ResNetBackbone(lda_out_channels, in_chn, Bottleneck, [3, 4, 6, 3], **kwargs)
|
| 322 |
-
if pretrained:
|
| 323 |
-
save_model = model_zoo.load_url(model_urls['resnet50'])
|
| 324 |
-
model_dict = model.state_dict()
|
| 325 |
-
state_dict = {k: v for k, v in save_model.items() if k in model_dict.keys()}
|
| 326 |
-
model_dict.update(state_dict)
|
| 327 |
-
model.load_state_dict(model_dict)
|
| 328 |
-
else:
|
| 329 |
-
model.apply(weights_init_xavier)
|
| 330 |
-
return model
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
def weights_init_xavier(m):
|
| 334 |
-
classname = m.__class__.__name__
|
| 335 |
-
# print(classname)
|
| 336 |
-
# if isinstance(m, nn.Conv2d):
|
| 337 |
-
if classname.find('Conv') != -1:
|
| 338 |
-
init.kaiming_normal_(m.weight.data)
|
| 339 |
-
elif classname.find('Linear') != -1:
|
| 340 |
-
init.kaiming_normal_(m.weight.data)
|
| 341 |
-
elif classname.find('BatchNorm2d') != -1:
|
| 342 |
-
init.uniform_(m.weight.data, 1.0, 0.02)
|
| 343 |
-
init.constant_(m.bias.data, 0.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/inference_videos.py
DELETED
|
@@ -1,77 +0,0 @@
|
|
| 1 |
-
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import os
|
| 16 |
-
import subprocess
|
| 17 |
-
from tqdm import tqdm
|
| 18 |
-
import random
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
def inference_video_from_fileslist(
|
| 22 |
-
video_fileslist: str,
|
| 23 |
-
audio_fileslist: str,
|
| 24 |
-
output_dir: str,
|
| 25 |
-
unet_config_path: str,
|
| 26 |
-
ckpt_path: str,
|
| 27 |
-
guidance_scale: float,
|
| 28 |
-
seed: int = 42,
|
| 29 |
-
):
|
| 30 |
-
with open(video_fileslist, "r", encoding="utf-8") as file:
|
| 31 |
-
video_paths = [line.strip() for line in file.readlines()]
|
| 32 |
-
|
| 33 |
-
with open(audio_fileslist, "r", encoding="utf-8") as file:
|
| 34 |
-
audio_paths = [line.strip() for line in file.readlines()]
|
| 35 |
-
|
| 36 |
-
random.seed(seed)
|
| 37 |
-
|
| 38 |
-
output_dir = f"{output_dir}__{seed}"
|
| 39 |
-
os.makedirs(output_dir, exist_ok=True)
|
| 40 |
-
|
| 41 |
-
random.shuffle(video_paths)
|
| 42 |
-
random.shuffle(audio_paths)
|
| 43 |
-
|
| 44 |
-
min_length = min(len(video_paths), len(audio_paths))
|
| 45 |
-
|
| 46 |
-
video_paths = video_paths[:min_length]
|
| 47 |
-
audio_paths = audio_paths[:min_length]
|
| 48 |
-
|
| 49 |
-
random.shuffle(video_paths)
|
| 50 |
-
random.shuffle(audio_paths)
|
| 51 |
-
|
| 52 |
-
for index, video_path in tqdm(enumerate(video_paths), total=len(video_paths)):
|
| 53 |
-
audio_path = audio_paths[index]
|
| 54 |
-
video_name = os.path.basename(video_path)[:-4]
|
| 55 |
-
audio_name = os.path.basename(audio_path)[:-4]
|
| 56 |
-
video_out_path = os.path.join(output_dir, f"{video_name}__{audio_name}.mp4")
|
| 57 |
-
inference_command = (
|
| 58 |
-
f"python -m scripts.inference --enable_deepcache --guidance_scale {guidance_scale} --unet_config_path {unet_config_path} "
|
| 59 |
-
f"--video_path {video_path} --audio_path {audio_path} --video_out_path {video_out_path} --inference_ckpt_path {ckpt_path}"
|
| 60 |
-
)
|
| 61 |
-
subprocess.run(inference_command, shell=True)
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
if __name__ == "__main__":
|
| 65 |
-
video_fileslist = "/mnt/bn/maliva-gen-ai-v2/chunyu.li/fileslist/video_fileslist.txt"
|
| 66 |
-
audio_fileslist = "/mnt/bn/maliva-gen-ai-v2/chunyu.li/fileslist/audio_fileslist.txt"
|
| 67 |
-
output_dir = "/mnt/bn/maliva-gen-ai-v2/chunyu.li/inference_videos_results"
|
| 68 |
-
|
| 69 |
-
unet_config_path = "configs/unet/stage2_512.yaml"
|
| 70 |
-
ckpt_path = "checkpoints/latentsync_unet.pt"
|
| 71 |
-
guidance_scale = 1.5
|
| 72 |
-
|
| 73 |
-
seed = 42
|
| 74 |
-
|
| 75 |
-
inference_video_from_fileslist(
|
| 76 |
-
video_fileslist, audio_fileslist, output_dir, unet_config_path, ckpt_path, guidance_scale, seed
|
| 77 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/syncnet/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
from .syncnet_eval import SyncNetEval
|
|
|
|
|
|
eval/syncnet/syncnet.py
DELETED
|
@@ -1,113 +0,0 @@
|
|
| 1 |
-
# https://github.com/joonson/syncnet_python/blob/master/SyncNetModel.py
|
| 2 |
-
|
| 3 |
-
import torch
|
| 4 |
-
import torch.nn as nn
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
def save(model, filename):
|
| 8 |
-
with open(filename, "wb") as f:
|
| 9 |
-
torch.save(model, f)
|
| 10 |
-
print("%s saved." % filename)
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
def load(filename):
|
| 14 |
-
net = torch.load(filename)
|
| 15 |
-
return net
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
class S(nn.Module):
|
| 19 |
-
def __init__(self, num_layers_in_fc_layers=1024):
|
| 20 |
-
super(S, self).__init__()
|
| 21 |
-
|
| 22 |
-
self.__nFeatures__ = 24
|
| 23 |
-
self.__nChs__ = 32
|
| 24 |
-
self.__midChs__ = 32
|
| 25 |
-
|
| 26 |
-
self.netcnnaud = nn.Sequential(
|
| 27 |
-
nn.Conv2d(1, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
|
| 28 |
-
nn.BatchNorm2d(64),
|
| 29 |
-
nn.ReLU(inplace=True),
|
| 30 |
-
nn.MaxPool2d(kernel_size=(1, 1), stride=(1, 1)),
|
| 31 |
-
nn.Conv2d(64, 192, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)),
|
| 32 |
-
nn.BatchNorm2d(192),
|
| 33 |
-
nn.ReLU(inplace=True),
|
| 34 |
-
nn.MaxPool2d(kernel_size=(3, 3), stride=(1, 2)),
|
| 35 |
-
nn.Conv2d(192, 384, kernel_size=(3, 3), padding=(1, 1)),
|
| 36 |
-
nn.BatchNorm2d(384),
|
| 37 |
-
nn.ReLU(inplace=True),
|
| 38 |
-
nn.Conv2d(384, 256, kernel_size=(3, 3), padding=(1, 1)),
|
| 39 |
-
nn.BatchNorm2d(256),
|
| 40 |
-
nn.ReLU(inplace=True),
|
| 41 |
-
nn.Conv2d(256, 256, kernel_size=(3, 3), padding=(1, 1)),
|
| 42 |
-
nn.BatchNorm2d(256),
|
| 43 |
-
nn.ReLU(inplace=True),
|
| 44 |
-
nn.MaxPool2d(kernel_size=(3, 3), stride=(2, 2)),
|
| 45 |
-
nn.Conv2d(256, 512, kernel_size=(5, 4), padding=(0, 0)),
|
| 46 |
-
nn.BatchNorm2d(512),
|
| 47 |
-
nn.ReLU(),
|
| 48 |
-
)
|
| 49 |
-
|
| 50 |
-
self.netfcaud = nn.Sequential(
|
| 51 |
-
nn.Linear(512, 512),
|
| 52 |
-
nn.BatchNorm1d(512),
|
| 53 |
-
nn.ReLU(),
|
| 54 |
-
nn.Linear(512, num_layers_in_fc_layers),
|
| 55 |
-
)
|
| 56 |
-
|
| 57 |
-
self.netfclip = nn.Sequential(
|
| 58 |
-
nn.Linear(512, 512),
|
| 59 |
-
nn.BatchNorm1d(512),
|
| 60 |
-
nn.ReLU(),
|
| 61 |
-
nn.Linear(512, num_layers_in_fc_layers),
|
| 62 |
-
)
|
| 63 |
-
|
| 64 |
-
self.netcnnlip = nn.Sequential(
|
| 65 |
-
nn.Conv3d(3, 96, kernel_size=(5, 7, 7), stride=(1, 2, 2), padding=0),
|
| 66 |
-
nn.BatchNorm3d(96),
|
| 67 |
-
nn.ReLU(inplace=True),
|
| 68 |
-
nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 2, 2)),
|
| 69 |
-
nn.Conv3d(96, 256, kernel_size=(1, 5, 5), stride=(1, 2, 2), padding=(0, 1, 1)),
|
| 70 |
-
nn.BatchNorm3d(256),
|
| 71 |
-
nn.ReLU(inplace=True),
|
| 72 |
-
nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 2, 2), padding=(0, 1, 1)),
|
| 73 |
-
nn.Conv3d(256, 256, kernel_size=(1, 3, 3), padding=(0, 1, 1)),
|
| 74 |
-
nn.BatchNorm3d(256),
|
| 75 |
-
nn.ReLU(inplace=True),
|
| 76 |
-
nn.Conv3d(256, 256, kernel_size=(1, 3, 3), padding=(0, 1, 1)),
|
| 77 |
-
nn.BatchNorm3d(256),
|
| 78 |
-
nn.ReLU(inplace=True),
|
| 79 |
-
nn.Conv3d(256, 256, kernel_size=(1, 3, 3), padding=(0, 1, 1)),
|
| 80 |
-
nn.BatchNorm3d(256),
|
| 81 |
-
nn.ReLU(inplace=True),
|
| 82 |
-
nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 2, 2)),
|
| 83 |
-
nn.Conv3d(256, 512, kernel_size=(1, 6, 6), padding=0),
|
| 84 |
-
nn.BatchNorm3d(512),
|
| 85 |
-
nn.ReLU(inplace=True),
|
| 86 |
-
)
|
| 87 |
-
|
| 88 |
-
def forward_aud(self, x):
|
| 89 |
-
|
| 90 |
-
mid = self.netcnnaud(x)
|
| 91 |
-
# N x ch x 24 x M
|
| 92 |
-
mid = mid.view((mid.size()[0], -1))
|
| 93 |
-
# N x (ch x 24)
|
| 94 |
-
out = self.netfcaud(mid)
|
| 95 |
-
|
| 96 |
-
return out
|
| 97 |
-
|
| 98 |
-
def forward_lip(self, x):
|
| 99 |
-
|
| 100 |
-
mid = self.netcnnlip(x)
|
| 101 |
-
mid = mid.view((mid.size()[0], -1))
|
| 102 |
-
# N x (ch x 24)
|
| 103 |
-
out = self.netfclip(mid)
|
| 104 |
-
|
| 105 |
-
return out
|
| 106 |
-
|
| 107 |
-
def forward_lipfeat(self, x):
|
| 108 |
-
|
| 109 |
-
mid = self.netcnnlip(x)
|
| 110 |
-
out = mid.view((mid.size()[0], -1))
|
| 111 |
-
# N x (ch x 24)
|
| 112 |
-
|
| 113 |
-
return out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/syncnet/syncnet_eval.py
DELETED
|
@@ -1,220 +0,0 @@
|
|
| 1 |
-
# Adapted from https://github.com/joonson/syncnet_python/blob/master/SyncNetInstance.py
|
| 2 |
-
|
| 3 |
-
import torch
|
| 4 |
-
import numpy
|
| 5 |
-
import time, pdb, argparse, subprocess, os, math, glob
|
| 6 |
-
import cv2
|
| 7 |
-
import python_speech_features
|
| 8 |
-
|
| 9 |
-
from scipy import signal
|
| 10 |
-
from scipy.io import wavfile
|
| 11 |
-
from .syncnet import S
|
| 12 |
-
from shutil import rmtree
|
| 13 |
-
from latentsync.utils.util import check_model_and_download
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
# ==================== Get OFFSET ====================
|
| 17 |
-
|
| 18 |
-
# Video 25 FPS, Audio 16000HZ
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
def calc_pdist(feat1, feat2, vshift=10):
|
| 22 |
-
win_size = vshift * 2 + 1
|
| 23 |
-
|
| 24 |
-
feat2p = torch.nn.functional.pad(feat2, (0, 0, vshift, vshift))
|
| 25 |
-
|
| 26 |
-
dists = []
|
| 27 |
-
|
| 28 |
-
for i in range(0, len(feat1)):
|
| 29 |
-
|
| 30 |
-
dists.append(
|
| 31 |
-
torch.nn.functional.pairwise_distance(feat1[[i], :].repeat(win_size, 1), feat2p[i : i + win_size, :])
|
| 32 |
-
)
|
| 33 |
-
|
| 34 |
-
return dists
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
# ==================== MAIN DEF ====================
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
class SyncNetEval(torch.nn.Module):
|
| 41 |
-
def __init__(self, dropout=0, num_layers_in_fc_layers=1024, device="cpu"):
|
| 42 |
-
super().__init__()
|
| 43 |
-
|
| 44 |
-
self.__S__ = S(num_layers_in_fc_layers=num_layers_in_fc_layers).to(device)
|
| 45 |
-
self.device = device
|
| 46 |
-
|
| 47 |
-
def evaluate(self, video_path, temp_dir="temp", batch_size=20, vshift=15):
|
| 48 |
-
|
| 49 |
-
self.__S__.eval()
|
| 50 |
-
|
| 51 |
-
# ========== ==========
|
| 52 |
-
# Convert files
|
| 53 |
-
# ========== ==========
|
| 54 |
-
|
| 55 |
-
if os.path.exists(temp_dir):
|
| 56 |
-
rmtree(temp_dir)
|
| 57 |
-
|
| 58 |
-
os.makedirs(temp_dir)
|
| 59 |
-
|
| 60 |
-
# temp_video_path = os.path.join(temp_dir, "temp.mp4")
|
| 61 |
-
# command = f"ffmpeg -loglevel error -nostdin -y -i {video_path} -vf scale='224:224' {temp_video_path}"
|
| 62 |
-
# subprocess.call(command, shell=True)
|
| 63 |
-
|
| 64 |
-
command = f"ffmpeg -loglevel error -nostdin -y -i {video_path} -f image2 {os.path.join(temp_dir, '%06d.jpg')}"
|
| 65 |
-
subprocess.call(command, shell=True, stdout=None)
|
| 66 |
-
|
| 67 |
-
command = f"ffmpeg -loglevel error -nostdin -y -i {video_path} -async 1 -ac 1 -vn -acodec pcm_s16le -ar 16000 {os.path.join(temp_dir, 'audio.wav')}"
|
| 68 |
-
subprocess.call(command, shell=True, stdout=None)
|
| 69 |
-
|
| 70 |
-
# ========== ==========
|
| 71 |
-
# Load video
|
| 72 |
-
# ========== ==========
|
| 73 |
-
|
| 74 |
-
images = []
|
| 75 |
-
|
| 76 |
-
flist = glob.glob(os.path.join(temp_dir, "*.jpg"))
|
| 77 |
-
flist.sort()
|
| 78 |
-
|
| 79 |
-
for fname in flist:
|
| 80 |
-
img_input = cv2.imread(fname)
|
| 81 |
-
img_input = cv2.resize(img_input, (224, 224)) # HARD CODED, CHANGE BEFORE RELEASE
|
| 82 |
-
images.append(img_input)
|
| 83 |
-
|
| 84 |
-
im = numpy.stack(images, axis=3)
|
| 85 |
-
im = numpy.expand_dims(im, axis=0)
|
| 86 |
-
im = numpy.transpose(im, (0, 3, 4, 1, 2))
|
| 87 |
-
|
| 88 |
-
imtv = torch.autograd.Variable(torch.from_numpy(im.astype(float)).float())
|
| 89 |
-
|
| 90 |
-
# ========== ==========
|
| 91 |
-
# Load audio
|
| 92 |
-
# ========== ==========
|
| 93 |
-
|
| 94 |
-
sample_rate, audio = wavfile.read(os.path.join(temp_dir, "audio.wav"))
|
| 95 |
-
mfcc = zip(*python_speech_features.mfcc(audio, sample_rate))
|
| 96 |
-
mfcc = numpy.stack([numpy.array(i) for i in mfcc])
|
| 97 |
-
|
| 98 |
-
cc = numpy.expand_dims(numpy.expand_dims(mfcc, axis=0), axis=0)
|
| 99 |
-
cct = torch.autograd.Variable(torch.from_numpy(cc.astype(float)).float())
|
| 100 |
-
|
| 101 |
-
# ========== ==========
|
| 102 |
-
# Check audio and video input length
|
| 103 |
-
# ========== ==========
|
| 104 |
-
|
| 105 |
-
# if (float(len(audio)) / 16000) != (float(len(images)) / 25):
|
| 106 |
-
# print(
|
| 107 |
-
# "WARNING: Audio (%.4fs) and video (%.4fs) lengths are different."
|
| 108 |
-
# % (float(len(audio)) / 16000, float(len(images)) / 25)
|
| 109 |
-
# )
|
| 110 |
-
|
| 111 |
-
min_length = min(len(images), math.floor(len(audio) / 640))
|
| 112 |
-
|
| 113 |
-
# ========== ==========
|
| 114 |
-
# Generate video and audio feats
|
| 115 |
-
# ========== ==========
|
| 116 |
-
|
| 117 |
-
lastframe = min_length - 5
|
| 118 |
-
im_feat = []
|
| 119 |
-
cc_feat = []
|
| 120 |
-
|
| 121 |
-
tS = time.time()
|
| 122 |
-
for i in range(0, lastframe, batch_size):
|
| 123 |
-
|
| 124 |
-
im_batch = [imtv[:, :, vframe : vframe + 5, :, :] for vframe in range(i, min(lastframe, i + batch_size))]
|
| 125 |
-
im_in = torch.cat(im_batch, 0)
|
| 126 |
-
im_out = self.__S__.forward_lip(im_in.to(self.device))
|
| 127 |
-
im_feat.append(im_out.data.cpu())
|
| 128 |
-
|
| 129 |
-
cc_batch = [
|
| 130 |
-
cct[:, :, :, vframe * 4 : vframe * 4 + 20] for vframe in range(i, min(lastframe, i + batch_size))
|
| 131 |
-
]
|
| 132 |
-
cc_in = torch.cat(cc_batch, 0)
|
| 133 |
-
cc_out = self.__S__.forward_aud(cc_in.to(self.device))
|
| 134 |
-
cc_feat.append(cc_out.data.cpu())
|
| 135 |
-
|
| 136 |
-
im_feat = torch.cat(im_feat, 0)
|
| 137 |
-
cc_feat = torch.cat(cc_feat, 0)
|
| 138 |
-
|
| 139 |
-
# ========== ==========
|
| 140 |
-
# Compute offset
|
| 141 |
-
# ========== ==========
|
| 142 |
-
|
| 143 |
-
dists = calc_pdist(im_feat, cc_feat, vshift=vshift)
|
| 144 |
-
mean_dists = torch.mean(torch.stack(dists, 1), 1)
|
| 145 |
-
|
| 146 |
-
min_dist, minidx = torch.min(mean_dists, 0)
|
| 147 |
-
|
| 148 |
-
av_offset = vshift - minidx
|
| 149 |
-
conf = torch.median(mean_dists) - min_dist
|
| 150 |
-
|
| 151 |
-
fdist = numpy.stack([dist[minidx].numpy() for dist in dists])
|
| 152 |
-
# fdist = numpy.pad(fdist, (3,3), 'constant', constant_values=15)
|
| 153 |
-
fconf = torch.median(mean_dists).numpy() - fdist
|
| 154 |
-
framewise_conf = signal.medfilt(fconf, kernel_size=9)
|
| 155 |
-
|
| 156 |
-
# numpy.set_printoptions(formatter={"float": "{: 0.3f}".format})
|
| 157 |
-
rmtree(temp_dir)
|
| 158 |
-
return av_offset.item(), min_dist.item(), conf.item()
|
| 159 |
-
|
| 160 |
-
def extract_feature(self, opt, videofile):
|
| 161 |
-
|
| 162 |
-
self.__S__.eval()
|
| 163 |
-
|
| 164 |
-
# ========== ==========
|
| 165 |
-
# Load video
|
| 166 |
-
# ========== ==========
|
| 167 |
-
cap = cv2.VideoCapture(videofile)
|
| 168 |
-
|
| 169 |
-
frame_num = 1
|
| 170 |
-
images = []
|
| 171 |
-
while frame_num:
|
| 172 |
-
frame_num += 1
|
| 173 |
-
ret, image = cap.read()
|
| 174 |
-
if ret == 0:
|
| 175 |
-
break
|
| 176 |
-
|
| 177 |
-
images.append(image)
|
| 178 |
-
|
| 179 |
-
im = numpy.stack(images, axis=3)
|
| 180 |
-
im = numpy.expand_dims(im, axis=0)
|
| 181 |
-
im = numpy.transpose(im, (0, 3, 4, 1, 2))
|
| 182 |
-
|
| 183 |
-
imtv = torch.autograd.Variable(torch.from_numpy(im.astype(float)).float())
|
| 184 |
-
|
| 185 |
-
# ========== ==========
|
| 186 |
-
# Generate video feats
|
| 187 |
-
# ========== ==========
|
| 188 |
-
|
| 189 |
-
lastframe = len(images) - 4
|
| 190 |
-
im_feat = []
|
| 191 |
-
|
| 192 |
-
tS = time.time()
|
| 193 |
-
for i in range(0, lastframe, opt.batch_size):
|
| 194 |
-
|
| 195 |
-
im_batch = [
|
| 196 |
-
imtv[:, :, vframe : vframe + 5, :, :] for vframe in range(i, min(lastframe, i + opt.batch_size))
|
| 197 |
-
]
|
| 198 |
-
im_in = torch.cat(im_batch, 0)
|
| 199 |
-
im_out = self.__S__.forward_lipfeat(im_in.to(self.device))
|
| 200 |
-
im_feat.append(im_out.data.cpu())
|
| 201 |
-
|
| 202 |
-
im_feat = torch.cat(im_feat, 0)
|
| 203 |
-
|
| 204 |
-
# ========== ==========
|
| 205 |
-
# Compute offset
|
| 206 |
-
# ========== ==========
|
| 207 |
-
|
| 208 |
-
print("Compute time %.3f sec." % (time.time() - tS))
|
| 209 |
-
|
| 210 |
-
return im_feat
|
| 211 |
-
|
| 212 |
-
def loadParameters(self, path):
|
| 213 |
-
check_model_and_download(path)
|
| 214 |
-
loaded_state = torch.load(path, map_location=lambda storage, loc: storage, weights_only=True)
|
| 215 |
-
|
| 216 |
-
self_state = self.__S__.state_dict()
|
| 217 |
-
|
| 218 |
-
for name, param in loaded_state.items():
|
| 219 |
-
|
| 220 |
-
self_state[name].copy_(param)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eval/syncnet_detect.py
DELETED
|
@@ -1,251 +0,0 @@
|
|
| 1 |
-
# Adapted from https://github.com/joonson/syncnet_python/blob/master/run_pipeline.py
|
| 2 |
-
|
| 3 |
-
import os, pdb, subprocess, glob, cv2
|
| 4 |
-
import numpy as np
|
| 5 |
-
from shutil import rmtree
|
| 6 |
-
import torch
|
| 7 |
-
|
| 8 |
-
from scenedetect.video_manager import VideoManager
|
| 9 |
-
from scenedetect.scene_manager import SceneManager
|
| 10 |
-
from scenedetect.stats_manager import StatsManager
|
| 11 |
-
from scenedetect.detectors import ContentDetector
|
| 12 |
-
|
| 13 |
-
from scipy.interpolate import interp1d
|
| 14 |
-
from scipy.io import wavfile
|
| 15 |
-
from scipy import signal
|
| 16 |
-
|
| 17 |
-
from eval.detectors import S3FD
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
class SyncNetDetector:
|
| 21 |
-
def __init__(self, device, detect_results_dir="detect_results"):
|
| 22 |
-
self.s3f_detector = S3FD(device=device)
|
| 23 |
-
self.detect_results_dir = detect_results_dir
|
| 24 |
-
|
| 25 |
-
def __call__(self, video_path: str, min_track=50, scale=False):
|
| 26 |
-
crop_dir = os.path.join(self.detect_results_dir, "crop")
|
| 27 |
-
video_dir = os.path.join(self.detect_results_dir, "video")
|
| 28 |
-
frames_dir = os.path.join(self.detect_results_dir, "frames")
|
| 29 |
-
temp_dir = os.path.join(self.detect_results_dir, "temp")
|
| 30 |
-
|
| 31 |
-
# ========== DELETE EXISTING DIRECTORIES ==========
|
| 32 |
-
if os.path.exists(crop_dir):
|
| 33 |
-
rmtree(crop_dir)
|
| 34 |
-
|
| 35 |
-
if os.path.exists(video_dir):
|
| 36 |
-
rmtree(video_dir)
|
| 37 |
-
|
| 38 |
-
if os.path.exists(frames_dir):
|
| 39 |
-
rmtree(frames_dir)
|
| 40 |
-
|
| 41 |
-
if os.path.exists(temp_dir):
|
| 42 |
-
rmtree(temp_dir)
|
| 43 |
-
|
| 44 |
-
# ========== MAKE NEW DIRECTORIES ==========
|
| 45 |
-
|
| 46 |
-
os.makedirs(crop_dir)
|
| 47 |
-
os.makedirs(video_dir)
|
| 48 |
-
os.makedirs(frames_dir)
|
| 49 |
-
os.makedirs(temp_dir)
|
| 50 |
-
|
| 51 |
-
# ========== CONVERT VIDEO AND EXTRACT FRAMES ==========
|
| 52 |
-
|
| 53 |
-
if scale:
|
| 54 |
-
scaled_video_path = os.path.join(video_dir, "scaled.mp4")
|
| 55 |
-
command = f"ffmpeg -loglevel error -y -nostdin -i {video_path} -vf scale='224:224' {scaled_video_path}"
|
| 56 |
-
subprocess.run(command, shell=True)
|
| 57 |
-
video_path = scaled_video_path
|
| 58 |
-
|
| 59 |
-
command = f"ffmpeg -y -nostdin -loglevel error -i {video_path} -qscale:v 2 -async 1 -r 25 {os.path.join(video_dir, 'video.mp4')}"
|
| 60 |
-
subprocess.run(command, shell=True, stdout=None)
|
| 61 |
-
|
| 62 |
-
command = f"ffmpeg -y -nostdin -loglevel error -i {os.path.join(video_dir, 'video.mp4')} -qscale:v 2 -f image2 {os.path.join(frames_dir, '%06d.jpg')}"
|
| 63 |
-
subprocess.run(command, shell=True, stdout=None)
|
| 64 |
-
|
| 65 |
-
command = f"ffmpeg -y -nostdin -loglevel error -i {os.path.join(video_dir, 'video.mp4')} -ac 1 -vn -acodec pcm_s16le -ar 16000 {os.path.join(video_dir, 'audio.wav')}"
|
| 66 |
-
subprocess.run(command, shell=True, stdout=None)
|
| 67 |
-
|
| 68 |
-
faces = self.detect_face(frames_dir)
|
| 69 |
-
|
| 70 |
-
scene = self.scene_detect(video_dir)
|
| 71 |
-
|
| 72 |
-
# Face tracking
|
| 73 |
-
alltracks = []
|
| 74 |
-
|
| 75 |
-
for shot in scene:
|
| 76 |
-
if shot[1].frame_num - shot[0].frame_num >= min_track:
|
| 77 |
-
alltracks.extend(self.track_face(faces[shot[0].frame_num : shot[1].frame_num], min_track=min_track))
|
| 78 |
-
|
| 79 |
-
# Face crop
|
| 80 |
-
for ii, track in enumerate(alltracks):
|
| 81 |
-
self.crop_video(track, os.path.join(crop_dir, "%05d" % ii), frames_dir, 25, temp_dir, video_dir)
|
| 82 |
-
|
| 83 |
-
rmtree(temp_dir)
|
| 84 |
-
|
| 85 |
-
def scene_detect(self, video_dir):
|
| 86 |
-
video_manager = VideoManager([os.path.join(video_dir, "video.mp4")])
|
| 87 |
-
stats_manager = StatsManager()
|
| 88 |
-
scene_manager = SceneManager(stats_manager)
|
| 89 |
-
# Add ContentDetector algorithm (constructor takes detector options like threshold).
|
| 90 |
-
scene_manager.add_detector(ContentDetector())
|
| 91 |
-
base_timecode = video_manager.get_base_timecode()
|
| 92 |
-
|
| 93 |
-
video_manager.set_downscale_factor()
|
| 94 |
-
|
| 95 |
-
video_manager.start()
|
| 96 |
-
|
| 97 |
-
scene_manager.detect_scenes(frame_source=video_manager)
|
| 98 |
-
|
| 99 |
-
scene_list = scene_manager.get_scene_list(base_timecode)
|
| 100 |
-
|
| 101 |
-
if scene_list == []:
|
| 102 |
-
scene_list = [(video_manager.get_base_timecode(), video_manager.get_current_timecode())]
|
| 103 |
-
|
| 104 |
-
return scene_list
|
| 105 |
-
|
| 106 |
-
def track_face(self, scenefaces, num_failed_det=25, min_track=50, min_face_size=100):
|
| 107 |
-
|
| 108 |
-
iouThres = 0.5 # Minimum IOU between consecutive face detections
|
| 109 |
-
tracks = []
|
| 110 |
-
|
| 111 |
-
while True:
|
| 112 |
-
track = []
|
| 113 |
-
for framefaces in scenefaces:
|
| 114 |
-
for face in framefaces:
|
| 115 |
-
if track == []:
|
| 116 |
-
track.append(face)
|
| 117 |
-
framefaces.remove(face)
|
| 118 |
-
elif face["frame"] - track[-1]["frame"] <= num_failed_det:
|
| 119 |
-
iou = bounding_box_iou(face["bbox"], track[-1]["bbox"])
|
| 120 |
-
if iou > iouThres:
|
| 121 |
-
track.append(face)
|
| 122 |
-
framefaces.remove(face)
|
| 123 |
-
continue
|
| 124 |
-
else:
|
| 125 |
-
break
|
| 126 |
-
|
| 127 |
-
if track == []:
|
| 128 |
-
break
|
| 129 |
-
elif len(track) > min_track:
|
| 130 |
-
|
| 131 |
-
framenum = np.array([f["frame"] for f in track])
|
| 132 |
-
bboxes = np.array([np.array(f["bbox"]) for f in track])
|
| 133 |
-
|
| 134 |
-
frame_i = np.arange(framenum[0], framenum[-1] + 1)
|
| 135 |
-
|
| 136 |
-
bboxes_i = []
|
| 137 |
-
for ij in range(0, 4):
|
| 138 |
-
interpfn = interp1d(framenum, bboxes[:, ij])
|
| 139 |
-
bboxes_i.append(interpfn(frame_i))
|
| 140 |
-
bboxes_i = np.stack(bboxes_i, axis=1)
|
| 141 |
-
|
| 142 |
-
if (
|
| 143 |
-
max(np.mean(bboxes_i[:, 2] - bboxes_i[:, 0]), np.mean(bboxes_i[:, 3] - bboxes_i[:, 1]))
|
| 144 |
-
> min_face_size
|
| 145 |
-
):
|
| 146 |
-
tracks.append({"frame": frame_i, "bbox": bboxes_i})
|
| 147 |
-
|
| 148 |
-
return tracks
|
| 149 |
-
|
| 150 |
-
def detect_face(self, frames_dir, facedet_scale=0.25):
|
| 151 |
-
flist = glob.glob(os.path.join(frames_dir, "*.jpg"))
|
| 152 |
-
flist.sort()
|
| 153 |
-
|
| 154 |
-
dets = []
|
| 155 |
-
|
| 156 |
-
for fidx, fname in enumerate(flist):
|
| 157 |
-
image = cv2.imread(fname)
|
| 158 |
-
|
| 159 |
-
image_np = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
| 160 |
-
bboxes = self.s3f_detector.detect_faces(image_np, conf_th=0.9, scales=[facedet_scale])
|
| 161 |
-
|
| 162 |
-
dets.append([])
|
| 163 |
-
for bbox in bboxes:
|
| 164 |
-
dets[-1].append({"frame": fidx, "bbox": (bbox[:-1]).tolist(), "conf": bbox[-1]})
|
| 165 |
-
|
| 166 |
-
return dets
|
| 167 |
-
|
| 168 |
-
def crop_video(self, track, cropfile, frames_dir, frame_rate, temp_dir, video_dir, crop_scale=0.4):
|
| 169 |
-
|
| 170 |
-
flist = glob.glob(os.path.join(frames_dir, "*.jpg"))
|
| 171 |
-
flist.sort()
|
| 172 |
-
|
| 173 |
-
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
| 174 |
-
vOut = cv2.VideoWriter(cropfile + "t.mp4", fourcc, frame_rate, (224, 224))
|
| 175 |
-
|
| 176 |
-
dets = {"x": [], "y": [], "s": []}
|
| 177 |
-
|
| 178 |
-
for det in track["bbox"]:
|
| 179 |
-
|
| 180 |
-
dets["s"].append(max((det[3] - det[1]), (det[2] - det[0])) / 2)
|
| 181 |
-
dets["y"].append((det[1] + det[3]) / 2) # crop center x
|
| 182 |
-
dets["x"].append((det[0] + det[2]) / 2) # crop center y
|
| 183 |
-
|
| 184 |
-
# Smooth detections
|
| 185 |
-
dets["s"] = signal.medfilt(dets["s"], kernel_size=13)
|
| 186 |
-
dets["x"] = signal.medfilt(dets["x"], kernel_size=13)
|
| 187 |
-
dets["y"] = signal.medfilt(dets["y"], kernel_size=13)
|
| 188 |
-
|
| 189 |
-
for fidx, frame in enumerate(track["frame"]):
|
| 190 |
-
|
| 191 |
-
cs = crop_scale
|
| 192 |
-
|
| 193 |
-
bs = dets["s"][fidx] # Detection box size
|
| 194 |
-
bsi = int(bs * (1 + 2 * cs)) # Pad videos by this amount
|
| 195 |
-
|
| 196 |
-
image = cv2.imread(flist[frame])
|
| 197 |
-
|
| 198 |
-
frame = np.pad(image, ((bsi, bsi), (bsi, bsi), (0, 0)), "constant", constant_values=(110, 110))
|
| 199 |
-
my = dets["y"][fidx] + bsi # BBox center Y
|
| 200 |
-
mx = dets["x"][fidx] + bsi # BBox center X
|
| 201 |
-
|
| 202 |
-
face = frame[int(my - bs) : int(my + bs * (1 + 2 * cs)), int(mx - bs * (1 + cs)) : int(mx + bs * (1 + cs))]
|
| 203 |
-
|
| 204 |
-
vOut.write(cv2.resize(face, (224, 224)))
|
| 205 |
-
|
| 206 |
-
audiotmp = os.path.join(temp_dir, "audio.wav")
|
| 207 |
-
audiostart = (track["frame"][0]) / frame_rate
|
| 208 |
-
audioend = (track["frame"][-1] + 1) / frame_rate
|
| 209 |
-
|
| 210 |
-
vOut.release()
|
| 211 |
-
|
| 212 |
-
# ========== CROP AUDIO FILE ==========
|
| 213 |
-
|
| 214 |
-
command = "ffmpeg -y -nostdin -loglevel error -i %s -ss %.3f -to %.3f %s" % (
|
| 215 |
-
os.path.join(video_dir, "audio.wav"),
|
| 216 |
-
audiostart,
|
| 217 |
-
audioend,
|
| 218 |
-
audiotmp,
|
| 219 |
-
)
|
| 220 |
-
output = subprocess.run(command, shell=True, stdout=None)
|
| 221 |
-
|
| 222 |
-
sample_rate, audio = wavfile.read(audiotmp)
|
| 223 |
-
|
| 224 |
-
# ========== COMBINE AUDIO AND VIDEO FILES ==========
|
| 225 |
-
|
| 226 |
-
command = "ffmpeg -y -nostdin -loglevel error -i %st.mp4 -i %s -c:v copy -c:a aac %s.mp4" % (
|
| 227 |
-
cropfile,
|
| 228 |
-
audiotmp,
|
| 229 |
-
cropfile,
|
| 230 |
-
)
|
| 231 |
-
output = subprocess.run(command, shell=True, stdout=None)
|
| 232 |
-
|
| 233 |
-
os.remove(cropfile + "t.mp4")
|
| 234 |
-
|
| 235 |
-
return {"track": track, "proc_track": dets}
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
def bounding_box_iou(boxA, boxB):
|
| 239 |
-
xA = max(boxA[0], boxB[0])
|
| 240 |
-
yA = max(boxA[1], boxB[1])
|
| 241 |
-
xB = min(boxA[2], boxB[2])
|
| 242 |
-
yB = min(boxA[3], boxB[3])
|
| 243 |
-
|
| 244 |
-
interArea = max(0, xB - xA) * max(0, yB - yA)
|
| 245 |
-
|
| 246 |
-
boxAArea = (boxA[2] - boxA[0]) * (boxA[3] - boxA[1])
|
| 247 |
-
boxBArea = (boxB[2] - boxB[0]) * (boxB[3] - boxB[1])
|
| 248 |
-
|
| 249 |
-
iou = interArea / float(boxAArea + boxBArea - interArea)
|
| 250 |
-
|
| 251 |
-
return iou
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
face_processing.py
DELETED
|
@@ -1,585 +0,0 @@
|
|
| 1 |
-
"""Face detection and region extraction for lipsync optimization (DEPRECATED - Pipeline handles this automatically)"""
|
| 2 |
-
|
| 3 |
-
# NOTE: All functions in this module are DEPRECATED.
|
| 4 |
-
# The lipsync pipeline (latentsync/pipelines/lipsync_pipeline.py) now handles:
|
| 5 |
-
# - Face detection
|
| 6 |
-
# - Affine transformation
|
| 7 |
-
# - Crop
|
| 8 |
-
# - Restore
|
| 9 |
-
# These functions are kept for reference but not used in the new workflow.
|
| 10 |
-
|
| 11 |
-
# import os
|
| 12 |
-
# import math
|
| 13 |
-
# import logging
|
| 14 |
-
# from typing import List, Dict, Tuple, Optional
|
| 15 |
-
#
|
| 16 |
-
# import cv2
|
| 17 |
-
# import numpy as np
|
| 18 |
-
# import mediapipe as mp
|
| 19 |
-
# from ffmpy import FFmpeg, FFRuntimeError
|
| 20 |
-
#
|
| 21 |
-
# from video_processing import get_video_info
|
| 22 |
-
#
|
| 23 |
-
# logger = logging.getLogger(__name__)
|
| 24 |
-
#
|
| 25 |
-
#
|
| 26 |
-
# class FaceDetectionError(Exception):
|
| 27 |
-
# """Custom exception for face detection errors"""
|
| 28 |
-
#
|
| 29 |
-
# pass
|
| 30 |
-
#
|
| 31 |
-
#
|
| 32 |
-
# def sample_frames_from_video(
|
| 33 |
-
# video_path: str, output_dir: str, sample_count: int = 5
|
| 34 |
-
# ) -> List[Tuple[int, str]]:
|
| 35 |
-
# """Extract uniform sample frames from video using OpenCV CUDA (HuggingFace)
|
| 36 |
-
#
|
| 37 |
-
# Args:
|
| 38 |
-
# video_path: Path to video
|
| 39 |
-
# output_dir: Directory to save extracted frames
|
| 40 |
-
# sample_count: Number of frames to sample
|
| 41 |
-
#
|
| 42 |
-
# Returns:
|
| 43 |
-
# List of (frame_index, frame_path) tuples
|
| 44 |
-
# """
|
| 45 |
-
# video_info = get_video_info(video_path)
|
| 46 |
-
# fps = video_info["fps"]
|
| 47 |
-
# duration = video_info["duration"]
|
| 48 |
-
# total_frames = int(duration * fps)
|
| 49 |
-
#
|
| 50 |
-
# frames_dir = os.path.join(output_dir, "sampled_frames")
|
| 51 |
-
# os.makedirs(frames_dir, exist_ok=True)
|
| 52 |
-
#
|
| 53 |
-
# if total_frames <= sample_count:
|
| 54 |
-
# frame_indices = list(range(total_frames))
|
| 55 |
-
# else:
|
| 56 |
-
# frame_indices = [
|
| 57 |
-
# int(i * total_frames / sample_count) for i in range(sample_count)
|
| 58 |
-
# ]
|
| 59 |
-
#
|
| 60 |
-
# extracted_frames = []
|
| 61 |
-
# cap = cv2.VideoCapture(video_path)
|
| 62 |
-
#
|
| 63 |
-
# try:
|
| 64 |
-
# for idx, frame_idx in enumerate(frame_indices):
|
| 65 |
-
# cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
|
| 66 |
-
# ret, frame = cap.read()
|
| 67 |
-
#
|
| 68 |
-
# if not ret or frame is None:
|
| 69 |
-
# logger.warning(f"Failed to read frame {frame_idx}")
|
| 70 |
-
# continue
|
| 71 |
-
#
|
| 72 |
-
# frame_path = os.path.join(frames_dir, f"frame_{idx:04d}.jpg")
|
| 73 |
-
# cv2.imwrite(frame_path, frame, [cv2.IMWRITE_JPEG_QUALITY, 90])
|
| 74 |
-
# extracted_frames.append((frame_idx, frame_path))
|
| 75 |
-
# finally:
|
| 76 |
-
# cap.release()
|
| 77 |
-
#
|
| 78 |
-
# logger.info(f"Extracted {len(extracted_frames)} frames from {video_path}")
|
| 79 |
-
# return extracted_frames
|
| 80 |
-
#
|
| 81 |
-
#
|
| 82 |
-
# def detect_faces_in_frames(
|
| 83 |
-
# extracted_frames: List[Tuple[int, str]],
|
| 84 |
-
# min_confidence: float = 0.5,
|
| 85 |
-
# min_face_pixels: int = 100,
|
| 86 |
-
# ) -> List[Dict]:
|
| 87 |
-
# """Detect faces in all sampled frames using MediaPipe Face Detection API
|
| 88 |
-
#
|
| 89 |
-
# Args:
|
| 90 |
-
# extracted_frames: List of (frame_index, frame_path) tuples
|
| 91 |
-
# min_confidence: Minimum detection confidence (0-1)
|
| 92 |
-
# min_face_pixels: Minimum face size in pixels
|
| 93 |
-
#
|
| 94 |
-
# Returns:
|
| 95 |
-
# List of detections: [{"frame_idx", "confidence", "bbox": (x, y, w, h)}]
|
| 96 |
-
# """
|
| 97 |
-
# detections = []
|
| 98 |
-
#
|
| 99 |
-
# with mp.solutions.face_detection.FaceDetection(
|
| 100 |
-
# model_selection=0, min_detection_confidence=min_confidence
|
| 101 |
-
# ) as face_detection:
|
| 102 |
-
# for frame_idx, frame_path in extracted_frames:
|
| 103 |
-
# frame = cv2.imread(frame_path)
|
| 104 |
-
# if frame is None:
|
| 105 |
-
# logger.warning(f"Failed to read frame: {frame_path}")
|
| 106 |
-
# continue
|
| 107 |
-
#
|
| 108 |
-
# h, w = frame.shape[:2]
|
| 109 |
-
# frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 110 |
-
#
|
| 111 |
-
# results = face_detection.process(frame_rgb)
|
| 112 |
-
#
|
| 113 |
-
# if results.detections:
|
| 114 |
-
# for detection in results.detections:
|
| 115 |
-
# bbox = detection.location_data.relative_bounding_box
|
| 116 |
-
#
|
| 117 |
-
# x = int(bbox.xmin * w)
|
| 118 |
-
# y = int(bbox.ymin * h)
|
| 119 |
-
# face_w = int(bbox.width * w)
|
| 120 |
-
# face_h = int(bbox.height * h)
|
| 121 |
-
#
|
| 122 |
-
# x = max(0, x)
|
| 123 |
-
# y = max(0, y)
|
| 124 |
-
# face_w = min(w - x, face_w)
|
| 125 |
-
# face_h = min(h - y, face_h)
|
| 126 |
-
#
|
| 127 |
-
# confidence = detection.score[0] if detection.score else 0.0
|
| 128 |
-
#
|
| 129 |
-
# if face_w >= min_face_pixels and face_h >= min_face_pixels:
|
| 130 |
-
# detections.append(
|
| 131 |
-
# {
|
| 132 |
-
# "frame_idx": frame_idx,
|
| 133 |
-
# "confidence": float(confidence),
|
| 134 |
-
# "bbox": (x, y, face_w, face_h),
|
| 135 |
-
# }
|
| 136 |
-
# )
|
| 137 |
-
#
|
| 138 |
-
# logger.info(f"Detected {len(detections)} faces in {len(extracted_frames)} frames")
|
| 139 |
-
# return detections
|
| 140 |
-
#
|
| 141 |
-
#
|
| 142 |
-
# def cluster_face_detections(
|
| 143 |
-
# detections: List[Dict], max_distance: int = 100
|
| 144 |
-
# ) -> List[List[Dict]]:
|
| 145 |
-
# """Group face detections belonging to the same person using clustering
|
| 146 |
-
#
|
| 147 |
-
# Args:
|
| 148 |
-
# detections: List of face detections
|
| 149 |
-
# max_distance: Maximum distance (pixels) to consider detections as same person
|
| 150 |
-
#
|
| 151 |
-
# Returns:
|
| 152 |
-
# List of clusters (each cluster is a list of detections)
|
| 153 |
-
# """
|
| 154 |
-
# if not detections:
|
| 155 |
-
# return []
|
| 156 |
-
#
|
| 157 |
-
# clusters = []
|
| 158 |
-
# visited = set()
|
| 159 |
-
#
|
| 160 |
-
# for i, det_i in enumerate(detections):
|
| 161 |
-
# if i in visited:
|
| 162 |
-
# continue
|
| 163 |
-
#
|
| 164 |
-
# x_i, y_i, w_i, h_i = det_i["bbox"]
|
| 165 |
-
# center_i = (x_i + w_i / 2, y_i + h_i / 2)
|
| 166 |
-
#
|
| 167 |
-
# cluster = [det_i]
|
| 168 |
-
# visited.add(i)
|
| 169 |
-
#
|
| 170 |
-
# for j, det_j in enumerate(detections):
|
| 171 |
-
# if j in visited:
|
| 172 |
-
# continue
|
| 173 |
-
#
|
| 174 |
-
# x_j, y_j, w_j, h_j = det_j["bbox"]
|
| 175 |
-
# center_j = (x_j + w_j / 2, y_j + h_j / 2)
|
| 176 |
-
#
|
| 177 |
-
# distance = math.sqrt(
|
| 178 |
-
# (center_i[0] - center_j[0]) ** 2 + (center_i[1] - center_j[1]) ** 2
|
| 179 |
-
# )
|
| 180 |
-
#
|
| 181 |
-
# if distance < max_distance:
|
| 182 |
-
# cluster.append(det_j)
|
| 183 |
-
# visited.add(j)
|
| 184 |
-
#
|
| 185 |
-
# clusters.append(cluster)
|
| 186 |
-
#
|
| 187 |
-
# logger.info(f"Clustered {len(detections)} detections into {len(clusters)} clusters")
|
| 188 |
-
# return clusters
|
| 189 |
-
#
|
| 190 |
-
#
|
| 191 |
-
# def select_best_cluster(clusters: List[List[Dict]]) -> Optional[List[Dict]]:
|
| 192 |
-
# """Select the best face cluster (highest frequency)
|
| 193 |
-
#
|
| 194 |
-
# Args:
|
| 195 |
-
# clusters: List of clusters
|
| 196 |
-
#
|
| 197 |
-
# Returns:
|
| 198 |
-
# Best cluster (most frequent) or None
|
| 199 |
-
# """
|
| 200 |
-
# if not clusters:
|
| 201 |
-
# return None
|
| 202 |
-
#
|
| 203 |
-
# scored_clusters = [(len(cluster), cluster) for cluster in clusters]
|
| 204 |
-
# scored_clusters.sort(key=lambda x: x[0], reverse=True)
|
| 205 |
-
#
|
| 206 |
-
# best_cluster = scored_clusters[0][1]
|
| 207 |
-
# logger.info(f"Selected best cluster with {len(best_cluster)} detections")
|
| 208 |
-
# return best_cluster
|
| 209 |
-
#
|
| 210 |
-
#
|
| 211 |
-
# def verify_face_stability(
|
| 212 |
-
# cluster: List[Dict], max_movement_percent: float = 0.3
|
| 213 |
-
# ) -> bool:
|
| 214 |
-
# """Verify face doesn't move too much between frames
|
| 215 |
-
#
|
| 216 |
-
# Args:
|
| 217 |
-
# cluster: Face detections for the same person
|
| 218 |
-
# max_movement_percent: Max movement as percentage of average face size
|
| 219 |
-
#
|
| 220 |
-
# Returns:
|
| 221 |
-
# True if face is stable, False otherwise
|
| 222 |
-
# """
|
| 223 |
-
# if len(cluster) < 2:
|
| 224 |
-
# return True
|
| 225 |
-
#
|
| 226 |
-
# centers = []
|
| 227 |
-
# sizes = []
|
| 228 |
-
#
|
| 229 |
-
# for det in cluster:
|
| 230 |
-
# x, y, w, h = det["bbox"]
|
| 231 |
-
# centers.append((x + w / 2, y + h / 2))
|
| 232 |
-
# sizes.append(w * h)
|
| 233 |
-
#
|
| 234 |
-
# avg_size = sum(sizes) / len(sizes)
|
| 235 |
-
# avg_face_dim = math.sqrt(avg_size)
|
| 236 |
-
# max_allowed_movement = avg_face_dim * max_movement_percent
|
| 237 |
-
#
|
| 238 |
-
# for i in range(len(centers) - 1):
|
| 239 |
-
# dx = abs(centers[i + 1][0] - centers[i][0])
|
| 240 |
-
# dy = abs(centers[i + 1][1] - centers[i][1])
|
| 241 |
-
# movement = math.sqrt(dx**2 + dy**2)
|
| 242 |
-
#
|
| 243 |
-
# if movement > max_allowed_movement:
|
| 244 |
-
# logger.warning(
|
| 245 |
-
# f"Face movement {movement:.1f}px > {max_allowed_movement:.1f}px"
|
| 246 |
-
# )
|
| 247 |
-
# return False
|
| 248 |
-
#
|
| 249 |
-
# return True
|
| 250 |
-
#
|
| 251 |
-
#
|
| 252 |
-
# def calculate_face_bbox_from_cluster(cluster: List[Dict]) -> Dict:
|
| 253 |
-
# """Calculate average face bounding box from cluster
|
| 254 |
-
#
|
| 255 |
-
# Args:
|
| 256 |
-
# cluster: Face detections for the same person
|
| 257 |
-
#
|
| 258 |
-
# Returns:
|
| 259 |
-
# Dict: {"x", "y", "width", "height"}
|
| 260 |
-
# """
|
| 261 |
-
# weighted_x = 0
|
| 262 |
-
# weighted_y = 0
|
| 263 |
-
# weighted_w = 0
|
| 264 |
-
# weighted_h = 0
|
| 265 |
-
# total_weight = 0
|
| 266 |
-
#
|
| 267 |
-
# for det in cluster:
|
| 268 |
-
# x, y, w, h = det["bbox"]
|
| 269 |
-
# weight = det["confidence"]
|
| 270 |
-
# weighted_x += x * weight
|
| 271 |
-
# weighted_y += y * weight
|
| 272 |
-
# weighted_w += w * weight
|
| 273 |
-
# weighted_h += h * weight
|
| 274 |
-
# total_weight += weight
|
| 275 |
-
#
|
| 276 |
-
# avg_bbox = {
|
| 277 |
-
# "x": int(weighted_x / total_weight),
|
| 278 |
-
# "y": int(weighted_y / total_weight),
|
| 279 |
-
# "width": int(weighted_w / total_weight),
|
| 280 |
-
# "height": int(weighted_h / total_weight),
|
| 281 |
-
# }
|
| 282 |
-
#
|
| 283 |
-
# return avg_bbox
|
| 284 |
-
#
|
| 285 |
-
#
|
| 286 |
-
# def calculate_safe_crop_size(
|
| 287 |
-
# face_bbox: Dict, video_width: int, video_height: int, crop_size: int = 512
|
| 288 |
-
# ) -> Dict:
|
| 289 |
-
# """Calculate safe crop region ensuring face is inside
|
| 290 |
-
#
|
| 291 |
-
# Args:
|
| 292 |
-
# face_bbox: Face bounding box {"x", "y", "width", "height"}
|
| 293 |
-
# video_width: Video width
|
| 294 |
-
# video_height: Video height
|
| 295 |
-
# crop_size: Size of crop region (default: 512)
|
| 296 |
-
#
|
| 297 |
-
# Returns:
|
| 298 |
-
# Dict: {"x", "y", "width", "height"}
|
| 299 |
-
# """
|
| 300 |
-
# crop_half = crop_size // 2
|
| 301 |
-
#
|
| 302 |
-
# face_center_x = face_bbox["x"] + face_bbox["width"] / 2
|
| 303 |
-
# face_center_y = face_bbox["y"] + face_bbox["height"] / 2
|
| 304 |
-
#
|
| 305 |
-
# crop_x = int(face_center_x - crop_half)
|
| 306 |
-
# crop_y = int(face_center_y - crop_half)
|
| 307 |
-
#
|
| 308 |
-
# crop_x = max(0, crop_x)
|
| 309 |
-
# crop_y = max(0, crop_y)
|
| 310 |
-
# crop_x = min(video_width - crop_size, crop_x)
|
| 311 |
-
# crop_y = min(video_height - crop_size, crop_y)
|
| 312 |
-
#
|
| 313 |
-
# face_right = face_bbox["x"] + face_bbox["width"]
|
| 314 |
-
# face_bottom = face_bbox["y"] + face_bbox["height"]
|
| 315 |
-
# crop_right = crop_x + crop_size
|
| 316 |
-
# crop_bottom = crop_y + crop_size
|
| 317 |
-
#
|
| 318 |
-
# if (
|
| 319 |
-
# face_bbox["x"] < crop_x
|
| 320 |
-
# or face_bbox["y"] < crop_y
|
| 321 |
-
# or face_right > crop_right
|
| 322 |
-
# or face_bottom > crop_bottom
|
| 323 |
-
# ):
|
| 324 |
-
# if face_bbox["x"] < crop_x:
|
| 325 |
-
# crop_x = face_bbox["x"]
|
| 326 |
-
# elif face_right > crop_right:
|
| 327 |
-
# crop_x = face_right - crop_size
|
| 328 |
-
#
|
| 329 |
-
# if face_bbox["y"] < crop_y:
|
| 330 |
-
# crop_y = face_bbox["y"]
|
| 331 |
-
# elif face_bottom > crop_bottom:
|
| 332 |
-
# crop_y = face_bottom - crop_size
|
| 333 |
-
#
|
| 334 |
-
# crop_x = max(0, crop_x)
|
| 335 |
-
# crop_y = max(0, crop_y)
|
| 336 |
-
# crop_x = min(video_width - crop_size, crop_x)
|
| 337 |
-
# crop_y = min(video_height - crop_size, crop_y)
|
| 338 |
-
#
|
| 339 |
-
# return {"x": crop_x, "y": crop_y, "width": crop_size, "height": crop_size}
|
| 340 |
-
#
|
| 341 |
-
#
|
| 342 |
-
# def detect_face_region(
|
| 343 |
-
# video_path: str,
|
| 344 |
-
# output_dir: str,
|
| 345 |
-
# crop_size: int = 512,
|
| 346 |
-
# sample_count: int = 20,
|
| 347 |
-
# min_confidence: float = 0.5,
|
| 348 |
-
# min_face_pixels: int = 100,
|
| 349 |
-
# max_face_movement_percent: float = 0.3,
|
| 350 |
-
# ) -> Dict:
|
| 351 |
-
# """Main function: Detect face and calculate safe crop (DEPRECATED - Pipeline handles this)
|
| 352 |
-
#
|
| 353 |
-
# Args:
|
| 354 |
-
# video_path: Path to video
|
| 355 |
-
# output_dir: Directory for temporary files
|
| 356 |
-
# crop_size: Size of crop region (default: 512)
|
| 357 |
-
# sample_count: Number of frames to sample (default: 20)
|
| 358 |
-
# min_confidence: Minimum detection confidence
|
| 359 |
-
# min_face_pixels: Minimum face size in pixels
|
| 360 |
-
# max_face_movement_percent: Max allowed face movement
|
| 361 |
-
#
|
| 362 |
-
# Returns:
|
| 363 |
-
# Dict: {"x", "y", "width", "height", "face_bbox"}
|
| 364 |
-
#
|
| 365 |
-
# Raises:
|
| 366 |
-
# FaceDetectionError: If face detection fails
|
| 367 |
-
# """
|
| 368 |
-
# try:
|
| 369 |
-
# logger.info(f"Starting face detection for: {video_path}")
|
| 370 |
-
# video_info = get_video_info(video_path)
|
| 371 |
-
# video_w, video_h = video_info["width"], video_info["height"]
|
| 372 |
-
# logger.info(
|
| 373 |
-
# f"Video: {video_w}x{video_h}, {video_info['fps']:.1f}fps, {video_info['duration']:.1f}s"
|
| 374 |
-
# )
|
| 375 |
-
#
|
| 376 |
-
# if video_w < crop_size or video_h < crop_size:
|
| 377 |
-
# raise FaceDetectionError(
|
| 378 |
-
# f"Video resolution {video_w}x{video_h} < {crop_size}x{crop_size}. "
|
| 379 |
-
# f"Please upload higher resolution video."
|
| 380 |
-
# )
|
| 381 |
-
#
|
| 382 |
-
# extracted_frames = sample_frames_from_video(
|
| 383 |
-
# video_path, output_dir, sample_count
|
| 384 |
-
# )
|
| 385 |
-
# logger.info(f"Sampled {len(extracted_frames)} frames for detection")
|
| 386 |
-
#
|
| 387 |
-
# detections = detect_faces_in_frames(
|
| 388 |
-
# extracted_frames, min_confidence, min_face_pixels
|
| 389 |
-
# )
|
| 390 |
-
#
|
| 391 |
-
# if not detections:
|
| 392 |
-
# raise FaceDetectionError(
|
| 393 |
-
# f"No face detected in {sample_count} sampled frames. "
|
| 394 |
-
# f"Please upload a video with a visible face."
|
| 395 |
-
# )
|
| 396 |
-
#
|
| 397 |
-
# logger.info(f"Found {len(detections)} face detections")
|
| 398 |
-
#
|
| 399 |
-
# frames_with_face = len(set(d["frame_idx"] for d in detections))
|
| 400 |
-
# face_coverage = frames_with_face / len(extracted_frames)
|
| 401 |
-
# logger.info(
|
| 402 |
-
# f"Face coverage: {frames_with_face}/{len(extracted_frames)} ({face_coverage * 100:.1f}%)"
|
| 403 |
-
# )
|
| 404 |
-
#
|
| 405 |
-
# if face_coverage < 0.5:
|
| 406 |
-
# raise FaceDetectionError(
|
| 407 |
-
# f"Face detected in only {frames_with_face}/{len(extracted_frames)} frames "
|
| 408 |
-
# f"({face_coverage * 100:.1f}%). "
|
| 409 |
-
# f"Please upload a video with a visible face."
|
| 410 |
-
# )
|
| 411 |
-
#
|
| 412 |
-
# clusters = cluster_face_detections(detections)
|
| 413 |
-
# logger.info(f"Grouped into {len(clusters)} face clusters")
|
| 414 |
-
#
|
| 415 |
-
# best_cluster = select_best_cluster(clusters)
|
| 416 |
-
#
|
| 417 |
-
# if best_cluster is None:
|
| 418 |
-
# raise FaceDetectionError(
|
| 419 |
-
# f"Failed to identify main face in video. "
|
| 420 |
-
# f"Please upload a video with a clear, visible face."
|
| 421 |
-
# )
|
| 422 |
-
#
|
| 423 |
-
# logger.info(f"Selected main face cluster with {len(best_cluster)} detections")
|
| 424 |
-
#
|
| 425 |
-
# if not verify_face_stability(best_cluster, max_face_movement_percent):
|
| 426 |
-
# raise FaceDetectionError(
|
| 427 |
-
# f"Face moves too much between frames. "
|
| 428 |
-
# f"Please upload a video with a stable face position."
|
| 429 |
-
# )
|
| 430 |
-
#
|
| 431 |
-
# logger.info("Face stability check passed")
|
| 432 |
-
#
|
| 433 |
-
# face_bbox = calculate_face_bbox_from_cluster(best_cluster)
|
| 434 |
-
# crop_bbox = calculate_safe_crop_size(face_bbox, video_w, video_h, crop_size)
|
| 435 |
-
#
|
| 436 |
-
# crop_bbox["face_bbox"] = face_bbox
|
| 437 |
-
#
|
| 438 |
-
# logger.info(
|
| 439 |
-
# f"Face detected at ({face_bbox['x']}, {face_bbox['y']}) "
|
| 440 |
-
# f"size {face_bbox['width']}x{face_bbox['height']}, "
|
| 441 |
-
# f"crop at ({crop_bbox['x']}, {crop_bbox['y']})"
|
| 442 |
-
# )
|
| 443 |
-
# logger.info("Face detection completed successfully")
|
| 444 |
-
#
|
| 445 |
-
# return crop_bbox
|
| 446 |
-
#
|
| 447 |
-
# except FaceDetectionError:
|
| 448 |
-
# raise
|
| 449 |
-
# except Exception as e:
|
| 450 |
-
# logger.error(f"Face detection failed: {e}")
|
| 451 |
-
# raise FaceDetectionError(f"Face detection failed: {str(e)}")
|
| 452 |
-
#
|
| 453 |
-
#
|
| 454 |
-
# def crop_video_to_size(
|
| 455 |
-
# video_path: str, crop_bbox: Dict, output_dir: str, crop_size: int = 512
|
| 456 |
-
# ) -> str:
|
| 457 |
-
# """Crop video to specified size using calculated bbox (DEPRECATED - Pipeline handles this)
|
| 458 |
-
#
|
| 459 |
-
# Args:
|
| 460 |
-
# video_path: Path to input video
|
| 461 |
-
# crop_bbox: Crop region {"x", "y", "width", "height"}
|
| 462 |
-
# output_dir: Directory to save output
|
| 463 |
-
# crop_size: Size of crop region (default: 512)
|
| 464 |
-
#
|
| 465 |
-
# Returns:
|
| 466 |
-
# Path to cropped video
|
| 467 |
-
# """
|
| 468 |
-
# output_path = os.path.join(output_dir, f"face_cropped_{crop_size}x{crop_size}.mp4")
|
| 469 |
-
#
|
| 470 |
-
# logger.info(
|
| 471 |
-
# f"Crop box: x={crop_bbox['x']}, y={crop_bbox['y']}, "
|
| 472 |
-
# f"width={crop_bbox['width']}, height={crop_bbox['height']}"
|
| 473 |
-
# )
|
| 474 |
-
#
|
| 475 |
-
# ffmpeg = FFmpeg(
|
| 476 |
-
# inputs={video_path: None},
|
| 477 |
-
# outputs={
|
| 478 |
-
# output_path: [
|
| 479 |
-
# "-vf",
|
| 480 |
-
# f"crop={crop_bbox['width']}:{crop_bbox['height']}:{crop_bbox['x']}:{crop_bbox['y']}",
|
| 481 |
-
# "-c:v",
|
| 482 |
-
# "libx264",
|
| 483 |
-
# "-preset",
|
| 484 |
-
# "slow",
|
| 485 |
-
# "-crf",
|
| 486 |
-
# "18",
|
| 487 |
-
# "-profile:v",
|
| 488 |
-
# "high",
|
| 489 |
-
# "-pix_fmt",
|
| 490 |
-
# "yuv420p",
|
| 491 |
-
# "-c:a",
|
| 492 |
-
# "copy",
|
| 493 |
-
# "-loglevel",
|
| 494 |
-
# "error",
|
| 495 |
-
# "-y",
|
| 496 |
-
# ]
|
| 497 |
-
# },
|
| 498 |
-
# )
|
| 499 |
-
# try:
|
| 500 |
-
# ffmpeg.run()
|
| 501 |
-
# except FFRuntimeError as e:
|
| 502 |
-
# logger.error(f"FFmpeg failed: {e}")
|
| 503 |
-
# raise
|
| 504 |
-
# logger.info(f"Cropped video to {crop_size}x{crop_size}: {output_path}")
|
| 505 |
-
# return output_path
|
| 506 |
-
#
|
| 507 |
-
#
|
| 508 |
-
# def blend_face_into_original(
|
| 509 |
-
# original_video: str,
|
| 510 |
-
# face_video: str,
|
| 511 |
-
# crop_bbox: Dict,
|
| 512 |
-
# output_dir: str,
|
| 513 |
-
# lipsynced_info: Dict | None = None,
|
| 514 |
-
# feather: int = 15,
|
| 515 |
-
# ) -> str:
|
| 516 |
-
# """Blend face video back into original video with edge feather only (DEPRECATED - Pipeline handles this)
|
| 517 |
-
#
|
| 518 |
-
# Args:
|
| 519 |
-
# original_video: Path to original video
|
| 520 |
-
# face_video: Path to lipsynced face video (cropped)
|
| 521 |
-
# crop_bbox: Crop region {"x", "y", "width", "height"}
|
| 522 |
-
# output_dir: Directory to save output
|
| 523 |
-
# lipsynced_info: Info of lipsynced video {"width", "height"} (optional)
|
| 524 |
-
# feather: Feather radius for smooth blending at edges
|
| 525 |
-
#
|
| 526 |
-
# Returns:
|
| 527 |
-
# Path to blended video
|
| 528 |
-
# """
|
| 529 |
-
# output_path = os.path.join(output_dir, "face_blended.mp4")
|
| 530 |
-
#
|
| 531 |
-
# overlay_x = crop_bbox["x"]
|
| 532 |
-
# overlay_y = crop_bbox["y"]
|
| 533 |
-
#
|
| 534 |
-
# if lipsynced_info:
|
| 535 |
-
# face_width = lipsynced_info["width"]
|
| 536 |
-
# face_height = lipsynced_info["height"]
|
| 537 |
-
# logger.info(
|
| 538 |
-
# f"Blending {face_width}x{face_height} at ({overlay_x}, {overlay_y}) "
|
| 539 |
-
# f"(crop_bbox: {crop_bbox})"
|
| 540 |
-
# )
|
| 541 |
-
# else:
|
| 542 |
-
# face_width = crop_bbox["width"]
|
| 543 |
-
# face_height = crop_bbox["height"]
|
| 544 |
-
# logger.info(f"Blending at ({overlay_x}, {overlay_y})")
|
| 545 |
-
#
|
| 546 |
-
# mask_w = face_width
|
| 547 |
-
# mask_h = face_height
|
| 548 |
-
#
|
| 549 |
-
# feather_radius = 50
|
| 550 |
-
#
|
| 551 |
-
# ffmpeg = FFmpeg(
|
| 552 |
-
# inputs={original_video: None, face_video: None},
|
| 553 |
-
# outputs={
|
| 554 |
-
# output_path: [
|
| 555 |
-
# "-filter_complex",
|
| 556 |
-
# f"[0:v][1:v]overlay={overlay_x}:{overlay_y}",
|
| 557 |
-
# "-c:v",
|
| 558 |
-
# "libx264",
|
| 559 |
-
# "-preset",
|
| 560 |
-
# "slow",
|
| 561 |
-
# "-crf",
|
| 562 |
-
# "18",
|
| 563 |
-
# "-profile:v",
|
| 564 |
-
# "high",
|
| 565 |
-
# "-pix_fmt",
|
| 566 |
-
# "yuv420p",
|
| 567 |
-
# "-threads",
|
| 568 |
-
# "0",
|
| 569 |
-
# "-movflags",
|
| 570 |
-
# "+faststart",
|
| 571 |
-
# "-c:a",
|
| 572 |
-
# "copy",
|
| 573 |
-
# "-loglevel",
|
| 574 |
-
# "error",
|
| 575 |
-
# "-y",
|
| 576 |
-
# ]
|
| 577 |
-
# },
|
| 578 |
-
# )
|
| 579 |
-
# try:
|
| 580 |
-
# ffmpeg.run()
|
| 581 |
-
# except FFRuntimeError as e:
|
| 582 |
-
# logger.error(f"FFmpeg failed: {e}")
|
| 583 |
-
# raise
|
| 584 |
-
# logger.info(f"Blended face into original: {output_path}")
|
| 585 |
-
# return output_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
latentsync/data/syncnet_dataset.py
DELETED
|
@@ -1,139 +0,0 @@
|
|
| 1 |
-
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import os
|
| 16 |
-
import numpy as np
|
| 17 |
-
from torch.utils.data import Dataset
|
| 18 |
-
import torch
|
| 19 |
-
import random
|
| 20 |
-
from ..utils.util import gather_video_paths_recursively
|
| 21 |
-
from ..utils.image_processor import ImageProcessor
|
| 22 |
-
from ..utils.audio import melspectrogram
|
| 23 |
-
import math
|
| 24 |
-
from pathlib import Path
|
| 25 |
-
|
| 26 |
-
from decord import AudioReader, VideoReader, cpu
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
class SyncNetDataset(Dataset):
|
| 30 |
-
def __init__(self, data_dir: str, fileslist: str, config):
|
| 31 |
-
if fileslist != "":
|
| 32 |
-
with open(fileslist) as file:
|
| 33 |
-
self.video_paths = [line.rstrip() for line in file]
|
| 34 |
-
elif data_dir != "":
|
| 35 |
-
self.video_paths = gather_video_paths_recursively(data_dir)
|
| 36 |
-
else:
|
| 37 |
-
raise ValueError("data_dir and fileslist cannot be both empty")
|
| 38 |
-
|
| 39 |
-
self.resolution = config.data.resolution
|
| 40 |
-
self.num_frames = config.data.num_frames
|
| 41 |
-
|
| 42 |
-
self.mel_window_length = math.ceil(self.num_frames / 5 * 16)
|
| 43 |
-
|
| 44 |
-
self.audio_sample_rate = config.data.audio_sample_rate
|
| 45 |
-
self.video_fps = config.data.video_fps
|
| 46 |
-
self.image_processor = ImageProcessor(resolution=config.data.resolution)
|
| 47 |
-
self.audio_mel_cache_dir = config.data.audio_mel_cache_dir
|
| 48 |
-
Path(self.audio_mel_cache_dir).mkdir(parents=True, exist_ok=True)
|
| 49 |
-
|
| 50 |
-
def __len__(self):
|
| 51 |
-
return len(self.video_paths)
|
| 52 |
-
|
| 53 |
-
def read_audio(self, video_path: str):
|
| 54 |
-
ar = AudioReader(video_path, ctx=cpu(self.worker_id), sample_rate=self.audio_sample_rate)
|
| 55 |
-
original_mel = melspectrogram(ar[:].asnumpy().squeeze(0))
|
| 56 |
-
return torch.from_numpy(original_mel)
|
| 57 |
-
|
| 58 |
-
def crop_audio_window(self, original_mel, start_index):
|
| 59 |
-
start_idx = int(80.0 * (start_index / float(self.video_fps)))
|
| 60 |
-
end_idx = start_idx + self.mel_window_length
|
| 61 |
-
return original_mel[:, start_idx:end_idx].unsqueeze(0)
|
| 62 |
-
|
| 63 |
-
def get_frames(self, video_reader: VideoReader):
|
| 64 |
-
total_num_frames = len(video_reader)
|
| 65 |
-
|
| 66 |
-
start_idx = random.randint(0, total_num_frames - self.num_frames)
|
| 67 |
-
frames_index = np.arange(start_idx, start_idx + self.num_frames, dtype=int)
|
| 68 |
-
|
| 69 |
-
while True:
|
| 70 |
-
wrong_start_idx = random.randint(0, total_num_frames - self.num_frames)
|
| 71 |
-
if wrong_start_idx == start_idx:
|
| 72 |
-
continue
|
| 73 |
-
wrong_frames_index = np.arange(wrong_start_idx, wrong_start_idx + self.num_frames, dtype=int)
|
| 74 |
-
break
|
| 75 |
-
|
| 76 |
-
frames = video_reader.get_batch(frames_index).asnumpy()
|
| 77 |
-
wrong_frames = video_reader.get_batch(wrong_frames_index).asnumpy()
|
| 78 |
-
|
| 79 |
-
return frames, wrong_frames, start_idx
|
| 80 |
-
|
| 81 |
-
def worker_init_fn(self, worker_id):
|
| 82 |
-
self.worker_id = worker_id
|
| 83 |
-
|
| 84 |
-
def __getitem__(self, idx):
|
| 85 |
-
while True:
|
| 86 |
-
try:
|
| 87 |
-
idx = random.randint(0, len(self) - 1)
|
| 88 |
-
|
| 89 |
-
# Get video file path
|
| 90 |
-
video_path = self.video_paths[idx]
|
| 91 |
-
|
| 92 |
-
vr = VideoReader(video_path, ctx=cpu(self.worker_id))
|
| 93 |
-
|
| 94 |
-
if len(vr) < 2 * self.num_frames:
|
| 95 |
-
continue
|
| 96 |
-
|
| 97 |
-
frames, wrong_frames, start_idx = self.get_frames(vr)
|
| 98 |
-
|
| 99 |
-
mel_cache_path = os.path.join(
|
| 100 |
-
self.audio_mel_cache_dir, os.path.basename(video_path).replace(".mp4", "_mel.pt")
|
| 101 |
-
)
|
| 102 |
-
|
| 103 |
-
if os.path.isfile(mel_cache_path):
|
| 104 |
-
try:
|
| 105 |
-
original_mel = torch.load(mel_cache_path, weights_only=True)
|
| 106 |
-
except Exception as e:
|
| 107 |
-
print(f"{type(e).__name__} - {e} - {mel_cache_path}")
|
| 108 |
-
os.remove(mel_cache_path)
|
| 109 |
-
original_mel = self.read_audio(video_path)
|
| 110 |
-
torch.save(original_mel, mel_cache_path)
|
| 111 |
-
else:
|
| 112 |
-
original_mel = self.read_audio(video_path)
|
| 113 |
-
torch.save(original_mel, mel_cache_path)
|
| 114 |
-
|
| 115 |
-
mel = self.crop_audio_window(original_mel, start_idx)
|
| 116 |
-
|
| 117 |
-
if mel.shape[-1] != self.mel_window_length:
|
| 118 |
-
continue
|
| 119 |
-
|
| 120 |
-
if random.choice([True, False]):
|
| 121 |
-
y = torch.ones(1).float()
|
| 122 |
-
chosen_frames = frames
|
| 123 |
-
else:
|
| 124 |
-
y = torch.zeros(1).float()
|
| 125 |
-
chosen_frames = wrong_frames
|
| 126 |
-
|
| 127 |
-
chosen_frames = self.image_processor.process_images(chosen_frames)
|
| 128 |
-
|
| 129 |
-
vr.seek(0) # avoid memory leak
|
| 130 |
-
break
|
| 131 |
-
|
| 132 |
-
except Exception as e: # Handle the exception of face not detcted
|
| 133 |
-
print(f"{type(e).__name__} - {e} - {video_path}")
|
| 134 |
-
if "vr" in locals():
|
| 135 |
-
vr.seek(0) # avoid memory leak
|
| 136 |
-
|
| 137 |
-
sample = dict(frames=chosen_frames, audio_samples=mel, y=y)
|
| 138 |
-
|
| 139 |
-
return sample
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
latentsync/data/unet_dataset.py
DELETED
|
@@ -1,152 +0,0 @@
|
|
| 1 |
-
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import os
|
| 16 |
-
import math
|
| 17 |
-
import numpy as np
|
| 18 |
-
from torch.utils.data import Dataset
|
| 19 |
-
import torch
|
| 20 |
-
import random
|
| 21 |
-
import cv2
|
| 22 |
-
from ..utils.image_processor import ImageProcessor, load_fixed_mask
|
| 23 |
-
from ..utils.audio import melspectrogram
|
| 24 |
-
from decord import AudioReader, VideoReader, cpu
|
| 25 |
-
import torch.nn.functional as F
|
| 26 |
-
from pathlib import Path
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
class UNetDataset(Dataset):
|
| 30 |
-
def __init__(self, train_data_dir: str, config):
|
| 31 |
-
if config.data.train_fileslist != "":
|
| 32 |
-
with open(config.data.train_fileslist) as file:
|
| 33 |
-
self.video_paths = [line.rstrip() for line in file]
|
| 34 |
-
elif train_data_dir != "":
|
| 35 |
-
self.video_paths = []
|
| 36 |
-
for file in os.listdir(train_data_dir):
|
| 37 |
-
if file.endswith(".mp4"):
|
| 38 |
-
self.video_paths.append(os.path.join(train_data_dir, file))
|
| 39 |
-
else:
|
| 40 |
-
raise ValueError("data_dir and fileslist cannot be both empty")
|
| 41 |
-
|
| 42 |
-
self.resolution = config.data.resolution
|
| 43 |
-
self.num_frames = config.data.num_frames
|
| 44 |
-
|
| 45 |
-
self.mel_window_length = math.ceil(self.num_frames / 5 * 16)
|
| 46 |
-
|
| 47 |
-
self.audio_sample_rate = config.data.audio_sample_rate
|
| 48 |
-
self.video_fps = config.data.video_fps
|
| 49 |
-
self.image_processor = ImageProcessor(
|
| 50 |
-
self.resolution, mask_image=load_fixed_mask(self.resolution, config.data.mask_image_path)
|
| 51 |
-
)
|
| 52 |
-
self.load_audio_data = config.model.add_audio_layer and config.run.use_syncnet
|
| 53 |
-
self.audio_mel_cache_dir = config.data.audio_mel_cache_dir
|
| 54 |
-
Path(self.audio_mel_cache_dir).mkdir(parents=True, exist_ok=True)
|
| 55 |
-
|
| 56 |
-
def __len__(self):
|
| 57 |
-
return len(self.video_paths)
|
| 58 |
-
|
| 59 |
-
def read_audio(self, video_path: str):
|
| 60 |
-
ar = AudioReader(video_path, ctx=cpu(self.worker_id), sample_rate=self.audio_sample_rate)
|
| 61 |
-
original_mel = melspectrogram(ar[:].asnumpy().squeeze(0))
|
| 62 |
-
return torch.from_numpy(original_mel)
|
| 63 |
-
|
| 64 |
-
def crop_audio_window(self, original_mel, start_index):
|
| 65 |
-
start_idx = int(80.0 * (start_index / float(self.video_fps)))
|
| 66 |
-
end_idx = start_idx + self.mel_window_length
|
| 67 |
-
return original_mel[:, start_idx:end_idx].unsqueeze(0)
|
| 68 |
-
|
| 69 |
-
def get_frames(self, video_reader: VideoReader):
|
| 70 |
-
total_num_frames = len(video_reader)
|
| 71 |
-
|
| 72 |
-
start_idx = random.randint(0, total_num_frames - self.num_frames)
|
| 73 |
-
gt_frames_index = np.arange(start_idx, start_idx + self.num_frames, dtype=int)
|
| 74 |
-
|
| 75 |
-
while True:
|
| 76 |
-
ref_start_idx = random.randint(0, total_num_frames - self.num_frames)
|
| 77 |
-
if ref_start_idx > start_idx - self.num_frames and ref_start_idx < start_idx + self.num_frames:
|
| 78 |
-
continue
|
| 79 |
-
ref_frames_index = np.arange(ref_start_idx, ref_start_idx + self.num_frames, dtype=int)
|
| 80 |
-
break
|
| 81 |
-
|
| 82 |
-
gt_frames = video_reader.get_batch(gt_frames_index).asnumpy()
|
| 83 |
-
ref_frames = video_reader.get_batch(ref_frames_index).asnumpy()
|
| 84 |
-
|
| 85 |
-
return gt_frames, ref_frames, start_idx
|
| 86 |
-
|
| 87 |
-
def worker_init_fn(self, worker_id):
|
| 88 |
-
self.worker_id = worker_id
|
| 89 |
-
|
| 90 |
-
def __getitem__(self, idx):
|
| 91 |
-
while True:
|
| 92 |
-
try:
|
| 93 |
-
idx = random.randint(0, len(self) - 1)
|
| 94 |
-
|
| 95 |
-
# Get video file path
|
| 96 |
-
video_path = self.video_paths[idx]
|
| 97 |
-
|
| 98 |
-
vr = VideoReader(video_path, ctx=cpu(self.worker_id))
|
| 99 |
-
|
| 100 |
-
if len(vr) < 3 * self.num_frames:
|
| 101 |
-
continue
|
| 102 |
-
|
| 103 |
-
gt_frames, ref_frames, start_idx = self.get_frames(vr)
|
| 104 |
-
|
| 105 |
-
if self.load_audio_data:
|
| 106 |
-
mel_cache_path = os.path.join(
|
| 107 |
-
self.audio_mel_cache_dir, os.path.basename(video_path).replace(".mp4", "_mel.pt")
|
| 108 |
-
)
|
| 109 |
-
|
| 110 |
-
if os.path.isfile(mel_cache_path):
|
| 111 |
-
try:
|
| 112 |
-
original_mel = torch.load(mel_cache_path, weights_only=True)
|
| 113 |
-
except Exception as e:
|
| 114 |
-
print(f"{type(e).__name__} - {e} - {mel_cache_path}")
|
| 115 |
-
os.remove(mel_cache_path)
|
| 116 |
-
original_mel = self.read_audio(video_path)
|
| 117 |
-
torch.save(original_mel, mel_cache_path)
|
| 118 |
-
else:
|
| 119 |
-
original_mel = self.read_audio(video_path)
|
| 120 |
-
torch.save(original_mel, mel_cache_path)
|
| 121 |
-
|
| 122 |
-
mel = self.crop_audio_window(original_mel, start_idx)
|
| 123 |
-
|
| 124 |
-
if mel.shape[-1] != self.mel_window_length:
|
| 125 |
-
continue
|
| 126 |
-
else:
|
| 127 |
-
mel = []
|
| 128 |
-
|
| 129 |
-
gt_pixel_values, masked_pixel_values, masks = self.image_processor.prepare_masks_and_masked_images(
|
| 130 |
-
gt_frames, affine_transform=False
|
| 131 |
-
) # (f, c, h, w)
|
| 132 |
-
ref_pixel_values = self.image_processor.process_images(ref_frames)
|
| 133 |
-
|
| 134 |
-
vr.seek(0) # avoid memory leak
|
| 135 |
-
break
|
| 136 |
-
|
| 137 |
-
except Exception as e: # Handle the exception of face not detcted
|
| 138 |
-
print(f"{type(e).__name__} - {e} - {video_path}")
|
| 139 |
-
if "vr" in locals():
|
| 140 |
-
vr.seek(0) # avoid memory leak
|
| 141 |
-
|
| 142 |
-
sample = dict(
|
| 143 |
-
gt_pixel_values=gt_pixel_values,
|
| 144 |
-
masked_pixel_values=masked_pixel_values,
|
| 145 |
-
ref_pixel_values=ref_pixel_values,
|
| 146 |
-
mel=mel,
|
| 147 |
-
masks=masks,
|
| 148 |
-
video_path=video_path,
|
| 149 |
-
start_idx=start_idx,
|
| 150 |
-
)
|
| 151 |
-
|
| 152 |
-
return sample
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
latentsync/models/attention.py
DELETED
|
@@ -1,280 +0,0 @@
|
|
| 1 |
-
# Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention.py
|
| 2 |
-
|
| 3 |
-
from dataclasses import dataclass
|
| 4 |
-
from typing import Optional
|
| 5 |
-
|
| 6 |
-
import torch
|
| 7 |
-
import torch.nn.functional as F
|
| 8 |
-
from torch import nn
|
| 9 |
-
|
| 10 |
-
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 11 |
-
from diffusers.models import ModelMixin
|
| 12 |
-
from diffusers.utils import BaseOutput
|
| 13 |
-
from diffusers.models.attention import FeedForward, AdaLayerNorm
|
| 14 |
-
|
| 15 |
-
from einops import rearrange, repeat
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
@dataclass
|
| 19 |
-
class Transformer3DModelOutput(BaseOutput):
|
| 20 |
-
sample: torch.FloatTensor
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
class Transformer3DModel(ModelMixin, ConfigMixin):
|
| 24 |
-
@register_to_config
|
| 25 |
-
def __init__(
|
| 26 |
-
self,
|
| 27 |
-
num_attention_heads: int = 16,
|
| 28 |
-
attention_head_dim: int = 88,
|
| 29 |
-
in_channels: Optional[int] = None,
|
| 30 |
-
num_layers: int = 1,
|
| 31 |
-
dropout: float = 0.0,
|
| 32 |
-
norm_num_groups: int = 32,
|
| 33 |
-
cross_attention_dim: Optional[int] = None,
|
| 34 |
-
attention_bias: bool = False,
|
| 35 |
-
activation_fn: str = "geglu",
|
| 36 |
-
num_embeds_ada_norm: Optional[int] = None,
|
| 37 |
-
use_linear_projection: bool = False,
|
| 38 |
-
only_cross_attention: bool = False,
|
| 39 |
-
upcast_attention: bool = False,
|
| 40 |
-
add_audio_layer=False,
|
| 41 |
-
):
|
| 42 |
-
super().__init__()
|
| 43 |
-
self.use_linear_projection = use_linear_projection
|
| 44 |
-
self.num_attention_heads = num_attention_heads
|
| 45 |
-
self.attention_head_dim = attention_head_dim
|
| 46 |
-
inner_dim = num_attention_heads * attention_head_dim
|
| 47 |
-
|
| 48 |
-
# Define input layers
|
| 49 |
-
self.in_channels = in_channels
|
| 50 |
-
|
| 51 |
-
self.norm = torch.nn.GroupNorm(num_groups=norm_num_groups, num_channels=in_channels, eps=1e-6, affine=True)
|
| 52 |
-
if use_linear_projection:
|
| 53 |
-
self.proj_in = nn.Linear(in_channels, inner_dim)
|
| 54 |
-
else:
|
| 55 |
-
self.proj_in = nn.Conv2d(in_channels, inner_dim, kernel_size=1, stride=1, padding=0)
|
| 56 |
-
|
| 57 |
-
# Define transformers blocks
|
| 58 |
-
self.transformer_blocks = nn.ModuleList(
|
| 59 |
-
[
|
| 60 |
-
BasicTransformerBlock(
|
| 61 |
-
inner_dim,
|
| 62 |
-
num_attention_heads,
|
| 63 |
-
attention_head_dim,
|
| 64 |
-
dropout=dropout,
|
| 65 |
-
cross_attention_dim=cross_attention_dim,
|
| 66 |
-
activation_fn=activation_fn,
|
| 67 |
-
num_embeds_ada_norm=num_embeds_ada_norm,
|
| 68 |
-
attention_bias=attention_bias,
|
| 69 |
-
upcast_attention=upcast_attention,
|
| 70 |
-
add_audio_layer=add_audio_layer,
|
| 71 |
-
)
|
| 72 |
-
for d in range(num_layers)
|
| 73 |
-
]
|
| 74 |
-
)
|
| 75 |
-
|
| 76 |
-
# Define output layers
|
| 77 |
-
if use_linear_projection:
|
| 78 |
-
self.proj_out = nn.Linear(in_channels, inner_dim)
|
| 79 |
-
else:
|
| 80 |
-
self.proj_out = nn.Conv2d(inner_dim, in_channels, kernel_size=1, stride=1, padding=0)
|
| 81 |
-
|
| 82 |
-
def forward(self, hidden_states, encoder_hidden_states=None, timestep=None, return_dict: bool = True):
|
| 83 |
-
# Input
|
| 84 |
-
assert hidden_states.dim() == 5, f"Expected hidden_states to have ndim=5, but got ndim={hidden_states.dim()}."
|
| 85 |
-
video_length = hidden_states.shape[2]
|
| 86 |
-
hidden_states = rearrange(hidden_states, "b c f h w -> (b f) c h w")
|
| 87 |
-
|
| 88 |
-
batch, channel, height, weight = hidden_states.shape
|
| 89 |
-
residual = hidden_states
|
| 90 |
-
|
| 91 |
-
hidden_states = self.norm(hidden_states)
|
| 92 |
-
if not self.use_linear_projection:
|
| 93 |
-
hidden_states = self.proj_in(hidden_states)
|
| 94 |
-
inner_dim = hidden_states.shape[1]
|
| 95 |
-
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(batch, height * weight, inner_dim)
|
| 96 |
-
else:
|
| 97 |
-
inner_dim = hidden_states.shape[1]
|
| 98 |
-
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(batch, height * weight, inner_dim)
|
| 99 |
-
hidden_states = self.proj_in(hidden_states)
|
| 100 |
-
|
| 101 |
-
# Blocks
|
| 102 |
-
for block in self.transformer_blocks:
|
| 103 |
-
hidden_states = block(
|
| 104 |
-
hidden_states,
|
| 105 |
-
encoder_hidden_states=encoder_hidden_states,
|
| 106 |
-
timestep=timestep,
|
| 107 |
-
video_length=video_length,
|
| 108 |
-
)
|
| 109 |
-
|
| 110 |
-
# Output
|
| 111 |
-
if not self.use_linear_projection:
|
| 112 |
-
hidden_states = hidden_states.reshape(batch, height, weight, inner_dim).permute(0, 3, 1, 2).contiguous()
|
| 113 |
-
hidden_states = self.proj_out(hidden_states)
|
| 114 |
-
else:
|
| 115 |
-
hidden_states = self.proj_out(hidden_states)
|
| 116 |
-
hidden_states = hidden_states.reshape(batch, height, weight, inner_dim).permute(0, 3, 1, 2).contiguous()
|
| 117 |
-
|
| 118 |
-
output = hidden_states + residual
|
| 119 |
-
|
| 120 |
-
output = rearrange(output, "(b f) c h w -> b c f h w", f=video_length)
|
| 121 |
-
if not return_dict:
|
| 122 |
-
return (output,)
|
| 123 |
-
|
| 124 |
-
return Transformer3DModelOutput(sample=output)
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
class BasicTransformerBlock(nn.Module):
|
| 128 |
-
def __init__(
|
| 129 |
-
self,
|
| 130 |
-
dim: int,
|
| 131 |
-
num_attention_heads: int,
|
| 132 |
-
attention_head_dim: int,
|
| 133 |
-
dropout=0.0,
|
| 134 |
-
cross_attention_dim: Optional[int] = None,
|
| 135 |
-
activation_fn: str = "geglu",
|
| 136 |
-
num_embeds_ada_norm: Optional[int] = None,
|
| 137 |
-
attention_bias: bool = False,
|
| 138 |
-
upcast_attention: bool = False,
|
| 139 |
-
add_audio_layer=False,
|
| 140 |
-
):
|
| 141 |
-
super().__init__()
|
| 142 |
-
self.use_ada_layer_norm = num_embeds_ada_norm is not None
|
| 143 |
-
self.add_audio_layer = add_audio_layer
|
| 144 |
-
|
| 145 |
-
self.norm1 = AdaLayerNorm(dim, num_embeds_ada_norm) if self.use_ada_layer_norm else nn.LayerNorm(dim)
|
| 146 |
-
self.attn1 = Attention(
|
| 147 |
-
query_dim=dim,
|
| 148 |
-
heads=num_attention_heads,
|
| 149 |
-
dim_head=attention_head_dim,
|
| 150 |
-
dropout=dropout,
|
| 151 |
-
bias=attention_bias,
|
| 152 |
-
upcast_attention=upcast_attention,
|
| 153 |
-
)
|
| 154 |
-
|
| 155 |
-
# Cross-attn
|
| 156 |
-
if add_audio_layer:
|
| 157 |
-
self.norm2 = AdaLayerNorm(dim, num_embeds_ada_norm) if self.use_ada_layer_norm else nn.LayerNorm(dim)
|
| 158 |
-
self.attn2 = Attention(
|
| 159 |
-
query_dim=dim,
|
| 160 |
-
cross_attention_dim=cross_attention_dim,
|
| 161 |
-
heads=num_attention_heads,
|
| 162 |
-
dim_head=attention_head_dim,
|
| 163 |
-
dropout=dropout,
|
| 164 |
-
bias=attention_bias,
|
| 165 |
-
upcast_attention=upcast_attention,
|
| 166 |
-
)
|
| 167 |
-
else:
|
| 168 |
-
self.attn2 = None
|
| 169 |
-
|
| 170 |
-
# Feed-forward
|
| 171 |
-
self.ff = FeedForward(dim, dropout=dropout, activation_fn=activation_fn)
|
| 172 |
-
self.norm3 = nn.LayerNorm(dim)
|
| 173 |
-
|
| 174 |
-
def forward(
|
| 175 |
-
self, hidden_states, encoder_hidden_states=None, timestep=None, attention_mask=None, video_length=None
|
| 176 |
-
):
|
| 177 |
-
norm_hidden_states = (
|
| 178 |
-
self.norm1(hidden_states, timestep) if self.use_ada_layer_norm else self.norm1(hidden_states)
|
| 179 |
-
)
|
| 180 |
-
|
| 181 |
-
hidden_states = self.attn1(norm_hidden_states, attention_mask=attention_mask) + hidden_states
|
| 182 |
-
|
| 183 |
-
if self.attn2 is not None and encoder_hidden_states is not None:
|
| 184 |
-
if encoder_hidden_states.dim() == 4:
|
| 185 |
-
encoder_hidden_states = rearrange(encoder_hidden_states, "b f s d -> (b f) s d")
|
| 186 |
-
norm_hidden_states = (
|
| 187 |
-
self.norm2(hidden_states, timestep) if self.use_ada_layer_norm else self.norm2(hidden_states)
|
| 188 |
-
)
|
| 189 |
-
hidden_states = (
|
| 190 |
-
self.attn2(
|
| 191 |
-
norm_hidden_states, encoder_hidden_states=encoder_hidden_states, attention_mask=attention_mask
|
| 192 |
-
)
|
| 193 |
-
+ hidden_states
|
| 194 |
-
)
|
| 195 |
-
|
| 196 |
-
# Feed-forward
|
| 197 |
-
hidden_states = self.ff(self.norm3(hidden_states)) + hidden_states
|
| 198 |
-
|
| 199 |
-
return hidden_states
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
class Attention(nn.Module):
|
| 203 |
-
def __init__(
|
| 204 |
-
self,
|
| 205 |
-
query_dim: int,
|
| 206 |
-
cross_attention_dim: Optional[int] = None,
|
| 207 |
-
heads: int = 8,
|
| 208 |
-
dim_head: int = 64,
|
| 209 |
-
dropout: float = 0.0,
|
| 210 |
-
bias=False,
|
| 211 |
-
upcast_attention: bool = False,
|
| 212 |
-
upcast_softmax: bool = False,
|
| 213 |
-
norm_num_groups: Optional[int] = None,
|
| 214 |
-
):
|
| 215 |
-
super().__init__()
|
| 216 |
-
inner_dim = dim_head * heads
|
| 217 |
-
cross_attention_dim = cross_attention_dim if cross_attention_dim is not None else query_dim
|
| 218 |
-
self.upcast_attention = upcast_attention
|
| 219 |
-
self.upcast_softmax = upcast_softmax
|
| 220 |
-
|
| 221 |
-
self.scale = dim_head**-0.5
|
| 222 |
-
|
| 223 |
-
self.heads = heads
|
| 224 |
-
|
| 225 |
-
if norm_num_groups is not None:
|
| 226 |
-
self.group_norm = nn.GroupNorm(num_channels=inner_dim, num_groups=norm_num_groups, eps=1e-5, affine=True)
|
| 227 |
-
else:
|
| 228 |
-
self.group_norm = None
|
| 229 |
-
|
| 230 |
-
self.to_q = nn.Linear(query_dim, inner_dim, bias=bias)
|
| 231 |
-
self.to_k = nn.Linear(cross_attention_dim, inner_dim, bias=bias)
|
| 232 |
-
self.to_v = nn.Linear(cross_attention_dim, inner_dim, bias=bias)
|
| 233 |
-
|
| 234 |
-
self.to_out = nn.ModuleList([])
|
| 235 |
-
self.to_out.append(nn.Linear(inner_dim, query_dim))
|
| 236 |
-
self.to_out.append(nn.Dropout(dropout))
|
| 237 |
-
|
| 238 |
-
def split_heads(self, tensor):
|
| 239 |
-
batch_size, seq_len, dim = tensor.shape
|
| 240 |
-
tensor = tensor.reshape(batch_size, seq_len, self.heads, dim // self.heads)
|
| 241 |
-
tensor = tensor.permute(0, 2, 1, 3)
|
| 242 |
-
return tensor
|
| 243 |
-
|
| 244 |
-
def concat_heads(self, tensor):
|
| 245 |
-
batch_size, heads, seq_len, head_dim = tensor.shape
|
| 246 |
-
tensor = tensor.permute(0, 2, 1, 3)
|
| 247 |
-
tensor = tensor.reshape(batch_size, seq_len, heads * head_dim)
|
| 248 |
-
return tensor
|
| 249 |
-
|
| 250 |
-
def forward(self, hidden_states, encoder_hidden_states=None, attention_mask=None):
|
| 251 |
-
if self.group_norm is not None:
|
| 252 |
-
hidden_states = self.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)
|
| 253 |
-
|
| 254 |
-
query = self.to_q(hidden_states)
|
| 255 |
-
query = self.split_heads(query)
|
| 256 |
-
|
| 257 |
-
encoder_hidden_states = encoder_hidden_states if encoder_hidden_states is not None else hidden_states
|
| 258 |
-
key = self.to_k(encoder_hidden_states)
|
| 259 |
-
value = self.to_v(encoder_hidden_states)
|
| 260 |
-
|
| 261 |
-
key = self.split_heads(key)
|
| 262 |
-
value = self.split_heads(value)
|
| 263 |
-
|
| 264 |
-
if attention_mask is not None:
|
| 265 |
-
if attention_mask.shape[-1] != query.shape[1]:
|
| 266 |
-
target_length = query.shape[1]
|
| 267 |
-
attention_mask = F.pad(attention_mask, (0, target_length), value=0.0)
|
| 268 |
-
attention_mask = attention_mask.repeat_interleave(self.heads, dim=0)
|
| 269 |
-
|
| 270 |
-
# Use PyTorch native implementation of FlashAttention-2
|
| 271 |
-
hidden_states = F.scaled_dot_product_attention(query, key, value, attn_mask=attention_mask)
|
| 272 |
-
|
| 273 |
-
hidden_states = self.concat_heads(hidden_states)
|
| 274 |
-
|
| 275 |
-
# linear proj
|
| 276 |
-
hidden_states = self.to_out[0](hidden_states)
|
| 277 |
-
|
| 278 |
-
# dropout
|
| 279 |
-
hidden_states = self.to_out[1](hidden_states)
|
| 280 |
-
return hidden_states
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
latentsync/models/motion_module.py
DELETED
|
@@ -1,313 +0,0 @@
|
|
| 1 |
-
# Adapted from https://github.com/guoyww/AnimateDiff/blob/main/animatediff/models/motion_module.py
|
| 2 |
-
|
| 3 |
-
# Actually we don't use the motion module in the final version of LatentSync
|
| 4 |
-
# When we started the project, we used the codebase of AnimateDiff and tried motion module
|
| 5 |
-
# But the results are poor, and we decied to leave the code here for possible future usage
|
| 6 |
-
|
| 7 |
-
from dataclasses import dataclass
|
| 8 |
-
|
| 9 |
-
import torch
|
| 10 |
-
import torch.nn.functional as F
|
| 11 |
-
from torch import nn
|
| 12 |
-
|
| 13 |
-
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 14 |
-
from diffusers.models import ModelMixin
|
| 15 |
-
from diffusers.utils import BaseOutput
|
| 16 |
-
from diffusers.models.attention import FeedForward
|
| 17 |
-
from .attention import Attention
|
| 18 |
-
|
| 19 |
-
from einops import rearrange, repeat
|
| 20 |
-
import math
|
| 21 |
-
from .utils import zero_module
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
@dataclass
|
| 25 |
-
class TemporalTransformer3DModelOutput(BaseOutput):
|
| 26 |
-
sample: torch.FloatTensor
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
def get_motion_module(in_channels, motion_module_type: str, motion_module_kwargs: dict):
|
| 30 |
-
if motion_module_type == "Vanilla":
|
| 31 |
-
return VanillaTemporalModule(
|
| 32 |
-
in_channels=in_channels,
|
| 33 |
-
**motion_module_kwargs,
|
| 34 |
-
)
|
| 35 |
-
else:
|
| 36 |
-
raise ValueError
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
class VanillaTemporalModule(nn.Module):
|
| 40 |
-
def __init__(
|
| 41 |
-
self,
|
| 42 |
-
in_channels,
|
| 43 |
-
num_attention_heads=8,
|
| 44 |
-
num_transformer_block=2,
|
| 45 |
-
attention_block_types=("Temporal_Self", "Temporal_Self"),
|
| 46 |
-
cross_frame_attention_mode=None,
|
| 47 |
-
temporal_position_encoding=False,
|
| 48 |
-
temporal_position_encoding_max_len=24,
|
| 49 |
-
temporal_attention_dim_div=1,
|
| 50 |
-
zero_initialize=True,
|
| 51 |
-
):
|
| 52 |
-
super().__init__()
|
| 53 |
-
|
| 54 |
-
self.temporal_transformer = TemporalTransformer3DModel(
|
| 55 |
-
in_channels=in_channels,
|
| 56 |
-
num_attention_heads=num_attention_heads,
|
| 57 |
-
attention_head_dim=in_channels // num_attention_heads // temporal_attention_dim_div,
|
| 58 |
-
num_layers=num_transformer_block,
|
| 59 |
-
attention_block_types=attention_block_types,
|
| 60 |
-
cross_frame_attention_mode=cross_frame_attention_mode,
|
| 61 |
-
temporal_position_encoding=temporal_position_encoding,
|
| 62 |
-
temporal_position_encoding_max_len=temporal_position_encoding_max_len,
|
| 63 |
-
)
|
| 64 |
-
|
| 65 |
-
if zero_initialize:
|
| 66 |
-
self.temporal_transformer.proj_out = zero_module(self.temporal_transformer.proj_out)
|
| 67 |
-
|
| 68 |
-
def forward(self, input_tensor, temb, encoder_hidden_states, attention_mask=None, anchor_frame_idx=None):
|
| 69 |
-
hidden_states = input_tensor
|
| 70 |
-
hidden_states = self.temporal_transformer(hidden_states, encoder_hidden_states, attention_mask)
|
| 71 |
-
|
| 72 |
-
output = hidden_states
|
| 73 |
-
return output
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
class TemporalTransformer3DModel(nn.Module):
|
| 77 |
-
def __init__(
|
| 78 |
-
self,
|
| 79 |
-
in_channels,
|
| 80 |
-
num_attention_heads,
|
| 81 |
-
attention_head_dim,
|
| 82 |
-
num_layers,
|
| 83 |
-
attention_block_types=(
|
| 84 |
-
"Temporal_Self",
|
| 85 |
-
"Temporal_Self",
|
| 86 |
-
),
|
| 87 |
-
dropout=0.0,
|
| 88 |
-
norm_num_groups=32,
|
| 89 |
-
cross_attention_dim=768,
|
| 90 |
-
activation_fn="geglu",
|
| 91 |
-
attention_bias=False,
|
| 92 |
-
upcast_attention=False,
|
| 93 |
-
cross_frame_attention_mode=None,
|
| 94 |
-
temporal_position_encoding=False,
|
| 95 |
-
temporal_position_encoding_max_len=24,
|
| 96 |
-
):
|
| 97 |
-
super().__init__()
|
| 98 |
-
|
| 99 |
-
inner_dim = num_attention_heads * attention_head_dim
|
| 100 |
-
|
| 101 |
-
self.norm = torch.nn.GroupNorm(num_groups=norm_num_groups, num_channels=in_channels, eps=1e-6, affine=True)
|
| 102 |
-
self.proj_in = nn.Linear(in_channels, inner_dim)
|
| 103 |
-
|
| 104 |
-
self.transformer_blocks = nn.ModuleList(
|
| 105 |
-
[
|
| 106 |
-
TemporalTransformerBlock(
|
| 107 |
-
dim=inner_dim,
|
| 108 |
-
num_attention_heads=num_attention_heads,
|
| 109 |
-
attention_head_dim=attention_head_dim,
|
| 110 |
-
attention_block_types=attention_block_types,
|
| 111 |
-
dropout=dropout,
|
| 112 |
-
norm_num_groups=norm_num_groups,
|
| 113 |
-
cross_attention_dim=cross_attention_dim,
|
| 114 |
-
activation_fn=activation_fn,
|
| 115 |
-
attention_bias=attention_bias,
|
| 116 |
-
upcast_attention=upcast_attention,
|
| 117 |
-
cross_frame_attention_mode=cross_frame_attention_mode,
|
| 118 |
-
temporal_position_encoding=temporal_position_encoding,
|
| 119 |
-
temporal_position_encoding_max_len=temporal_position_encoding_max_len,
|
| 120 |
-
)
|
| 121 |
-
for d in range(num_layers)
|
| 122 |
-
]
|
| 123 |
-
)
|
| 124 |
-
self.proj_out = nn.Linear(inner_dim, in_channels)
|
| 125 |
-
|
| 126 |
-
def forward(self, hidden_states, encoder_hidden_states=None, attention_mask=None):
|
| 127 |
-
assert hidden_states.dim() == 5, f"Expected hidden_states to have ndim=5, but got ndim={hidden_states.dim()}."
|
| 128 |
-
video_length = hidden_states.shape[2]
|
| 129 |
-
hidden_states = rearrange(hidden_states, "b c f h w -> (b f) c h w")
|
| 130 |
-
|
| 131 |
-
batch, channel, height, weight = hidden_states.shape
|
| 132 |
-
residual = hidden_states
|
| 133 |
-
|
| 134 |
-
hidden_states = self.norm(hidden_states)
|
| 135 |
-
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(batch, height * weight, channel)
|
| 136 |
-
hidden_states = self.proj_in(hidden_states)
|
| 137 |
-
|
| 138 |
-
# Transformer Blocks
|
| 139 |
-
for block in self.transformer_blocks:
|
| 140 |
-
hidden_states = block(
|
| 141 |
-
hidden_states, encoder_hidden_states=encoder_hidden_states, video_length=video_length
|
| 142 |
-
)
|
| 143 |
-
|
| 144 |
-
# output
|
| 145 |
-
hidden_states = self.proj_out(hidden_states)
|
| 146 |
-
hidden_states = hidden_states.reshape(batch, height, weight, channel).permute(0, 3, 1, 2).contiguous()
|
| 147 |
-
|
| 148 |
-
output = hidden_states + residual
|
| 149 |
-
output = rearrange(output, "(b f) c h w -> b c f h w", f=video_length)
|
| 150 |
-
|
| 151 |
-
return output
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
class TemporalTransformerBlock(nn.Module):
|
| 155 |
-
def __init__(
|
| 156 |
-
self,
|
| 157 |
-
dim,
|
| 158 |
-
num_attention_heads,
|
| 159 |
-
attention_head_dim,
|
| 160 |
-
attention_block_types=(
|
| 161 |
-
"Temporal_Self",
|
| 162 |
-
"Temporal_Self",
|
| 163 |
-
),
|
| 164 |
-
dropout=0.0,
|
| 165 |
-
norm_num_groups=32,
|
| 166 |
-
cross_attention_dim=768,
|
| 167 |
-
activation_fn="geglu",
|
| 168 |
-
attention_bias=False,
|
| 169 |
-
upcast_attention=False,
|
| 170 |
-
cross_frame_attention_mode=None,
|
| 171 |
-
temporal_position_encoding=False,
|
| 172 |
-
temporal_position_encoding_max_len=24,
|
| 173 |
-
):
|
| 174 |
-
super().__init__()
|
| 175 |
-
|
| 176 |
-
attention_blocks = []
|
| 177 |
-
norms = []
|
| 178 |
-
|
| 179 |
-
for block_name in attention_block_types:
|
| 180 |
-
attention_blocks.append(
|
| 181 |
-
VersatileAttention(
|
| 182 |
-
attention_mode=block_name.split("_")[0],
|
| 183 |
-
cross_attention_dim=cross_attention_dim if block_name.endswith("_Cross") else None,
|
| 184 |
-
query_dim=dim,
|
| 185 |
-
heads=num_attention_heads,
|
| 186 |
-
dim_head=attention_head_dim,
|
| 187 |
-
dropout=dropout,
|
| 188 |
-
bias=attention_bias,
|
| 189 |
-
upcast_attention=upcast_attention,
|
| 190 |
-
cross_frame_attention_mode=cross_frame_attention_mode,
|
| 191 |
-
temporal_position_encoding=temporal_position_encoding,
|
| 192 |
-
temporal_position_encoding_max_len=temporal_position_encoding_max_len,
|
| 193 |
-
)
|
| 194 |
-
)
|
| 195 |
-
norms.append(nn.LayerNorm(dim))
|
| 196 |
-
|
| 197 |
-
self.attention_blocks = nn.ModuleList(attention_blocks)
|
| 198 |
-
self.norms = nn.ModuleList(norms)
|
| 199 |
-
|
| 200 |
-
self.ff = FeedForward(dim, dropout=dropout, activation_fn=activation_fn)
|
| 201 |
-
self.ff_norm = nn.LayerNorm(dim)
|
| 202 |
-
|
| 203 |
-
def forward(self, hidden_states, encoder_hidden_states=None, attention_mask=None, video_length=None):
|
| 204 |
-
for attention_block, norm in zip(self.attention_blocks, self.norms):
|
| 205 |
-
norm_hidden_states = norm(hidden_states)
|
| 206 |
-
hidden_states = (
|
| 207 |
-
attention_block(
|
| 208 |
-
norm_hidden_states,
|
| 209 |
-
encoder_hidden_states=encoder_hidden_states if attention_block.is_cross_attention else None,
|
| 210 |
-
video_length=video_length,
|
| 211 |
-
)
|
| 212 |
-
+ hidden_states
|
| 213 |
-
)
|
| 214 |
-
|
| 215 |
-
hidden_states = self.ff(self.ff_norm(hidden_states)) + hidden_states
|
| 216 |
-
|
| 217 |
-
output = hidden_states
|
| 218 |
-
return output
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
class PositionalEncoding(nn.Module):
|
| 222 |
-
def __init__(self, d_model, dropout=0.0, max_len=24):
|
| 223 |
-
super().__init__()
|
| 224 |
-
self.dropout = nn.Dropout(p=dropout)
|
| 225 |
-
position = torch.arange(max_len).unsqueeze(1)
|
| 226 |
-
div_term = torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model))
|
| 227 |
-
pe = torch.zeros(1, max_len, d_model)
|
| 228 |
-
pe[0, :, 0::2] = torch.sin(position * div_term)
|
| 229 |
-
pe[0, :, 1::2] = torch.cos(position * div_term)
|
| 230 |
-
self.register_buffer("pe", pe)
|
| 231 |
-
|
| 232 |
-
def forward(self, x):
|
| 233 |
-
x = x + self.pe[:, : x.size(1)]
|
| 234 |
-
return self.dropout(x)
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
class VersatileAttention(Attention):
|
| 238 |
-
def __init__(
|
| 239 |
-
self,
|
| 240 |
-
attention_mode=None,
|
| 241 |
-
cross_frame_attention_mode=None,
|
| 242 |
-
temporal_position_encoding=False,
|
| 243 |
-
temporal_position_encoding_max_len=24,
|
| 244 |
-
*args,
|
| 245 |
-
**kwargs,
|
| 246 |
-
):
|
| 247 |
-
super().__init__(*args, **kwargs)
|
| 248 |
-
assert attention_mode == "Temporal"
|
| 249 |
-
|
| 250 |
-
self.attention_mode = attention_mode
|
| 251 |
-
self.is_cross_attention = kwargs["cross_attention_dim"] is not None
|
| 252 |
-
|
| 253 |
-
self.pos_encoder = (
|
| 254 |
-
PositionalEncoding(kwargs["query_dim"], dropout=0.0, max_len=temporal_position_encoding_max_len)
|
| 255 |
-
if (temporal_position_encoding and attention_mode == "Temporal")
|
| 256 |
-
else None
|
| 257 |
-
)
|
| 258 |
-
|
| 259 |
-
def extra_repr(self):
|
| 260 |
-
return f"(Module Info) Attention_Mode: {self.attention_mode}, Is_Cross_Attention: {self.is_cross_attention}"
|
| 261 |
-
|
| 262 |
-
def forward(self, hidden_states, encoder_hidden_states=None, attention_mask=None, video_length=None):
|
| 263 |
-
if self.attention_mode == "Temporal":
|
| 264 |
-
s = hidden_states.shape[1]
|
| 265 |
-
hidden_states = rearrange(hidden_states, "(b f) s c -> (b s) f c", f=video_length)
|
| 266 |
-
|
| 267 |
-
if self.pos_encoder is not None:
|
| 268 |
-
hidden_states = self.pos_encoder(hidden_states)
|
| 269 |
-
|
| 270 |
-
##### This section will not be executed #####
|
| 271 |
-
encoder_hidden_states = (
|
| 272 |
-
repeat(encoder_hidden_states, "b n c -> (b s) n c", s=s)
|
| 273 |
-
if encoder_hidden_states is not None
|
| 274 |
-
else encoder_hidden_states
|
| 275 |
-
)
|
| 276 |
-
#############################################
|
| 277 |
-
else:
|
| 278 |
-
raise NotImplementedError
|
| 279 |
-
|
| 280 |
-
if self.group_norm is not None:
|
| 281 |
-
hidden_states = self.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)
|
| 282 |
-
|
| 283 |
-
query = self.to_q(hidden_states)
|
| 284 |
-
query = self.split_heads(query)
|
| 285 |
-
|
| 286 |
-
encoder_hidden_states = encoder_hidden_states if encoder_hidden_states is not None else hidden_states
|
| 287 |
-
key = self.to_k(encoder_hidden_states)
|
| 288 |
-
value = self.to_v(encoder_hidden_states)
|
| 289 |
-
|
| 290 |
-
key = self.split_heads(key)
|
| 291 |
-
value = self.split_heads(value)
|
| 292 |
-
|
| 293 |
-
if attention_mask is not None:
|
| 294 |
-
if attention_mask.shape[-1] != query.shape[1]:
|
| 295 |
-
target_length = query.shape[1]
|
| 296 |
-
attention_mask = F.pad(attention_mask, (0, target_length), value=0.0)
|
| 297 |
-
attention_mask = attention_mask.repeat_interleave(self.heads, dim=0)
|
| 298 |
-
|
| 299 |
-
# Use PyTorch native implementation of FlashAttention-2
|
| 300 |
-
hidden_states = F.scaled_dot_product_attention(query, key, value, attn_mask=attention_mask)
|
| 301 |
-
|
| 302 |
-
hidden_states = self.concat_heads(hidden_states)
|
| 303 |
-
|
| 304 |
-
# linear proj
|
| 305 |
-
hidden_states = self.to_out[0](hidden_states)
|
| 306 |
-
|
| 307 |
-
# dropout
|
| 308 |
-
hidden_states = self.to_out[1](hidden_states)
|
| 309 |
-
|
| 310 |
-
if self.attention_mode == "Temporal":
|
| 311 |
-
hidden_states = rearrange(hidden_states, "(b s) f c -> (b f) s c", s=s)
|
| 312 |
-
|
| 313 |
-
return hidden_states
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
latentsync/models/resnet.py
DELETED
|
@@ -1,228 +0,0 @@
|
|
| 1 |
-
# Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/resnet.py
|
| 2 |
-
|
| 3 |
-
import torch
|
| 4 |
-
import torch.nn as nn
|
| 5 |
-
import torch.nn.functional as F
|
| 6 |
-
|
| 7 |
-
from einops import rearrange
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
class InflatedConv3d(nn.Conv2d):
|
| 11 |
-
def forward(self, x):
|
| 12 |
-
video_length = x.shape[2]
|
| 13 |
-
|
| 14 |
-
x = rearrange(x, "b c f h w -> (b f) c h w")
|
| 15 |
-
x = super().forward(x)
|
| 16 |
-
x = rearrange(x, "(b f) c h w -> b c f h w", f=video_length)
|
| 17 |
-
|
| 18 |
-
return x
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
class InflatedGroupNorm(nn.GroupNorm):
|
| 22 |
-
def forward(self, x):
|
| 23 |
-
video_length = x.shape[2]
|
| 24 |
-
|
| 25 |
-
x = rearrange(x, "b c f h w -> (b f) c h w")
|
| 26 |
-
x = super().forward(x)
|
| 27 |
-
x = rearrange(x, "(b f) c h w -> b c f h w", f=video_length)
|
| 28 |
-
|
| 29 |
-
return x
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
class Upsample3D(nn.Module):
|
| 33 |
-
def __init__(self, channels, use_conv=False, use_conv_transpose=False, out_channels=None, name="conv"):
|
| 34 |
-
super().__init__()
|
| 35 |
-
self.channels = channels
|
| 36 |
-
self.out_channels = out_channels or channels
|
| 37 |
-
self.use_conv = use_conv
|
| 38 |
-
self.use_conv_transpose = use_conv_transpose
|
| 39 |
-
self.name = name
|
| 40 |
-
|
| 41 |
-
conv = None
|
| 42 |
-
if use_conv_transpose:
|
| 43 |
-
raise NotImplementedError
|
| 44 |
-
elif use_conv:
|
| 45 |
-
self.conv = InflatedConv3d(self.channels, self.out_channels, 3, padding=1)
|
| 46 |
-
|
| 47 |
-
def forward(self, hidden_states, output_size=None):
|
| 48 |
-
assert hidden_states.shape[1] == self.channels
|
| 49 |
-
|
| 50 |
-
if self.use_conv_transpose:
|
| 51 |
-
raise NotImplementedError
|
| 52 |
-
|
| 53 |
-
# Cast to float32 to as 'upsample_nearest2d_out_frame' op does not support bfloat16
|
| 54 |
-
dtype = hidden_states.dtype
|
| 55 |
-
if dtype == torch.bfloat16:
|
| 56 |
-
hidden_states = hidden_states.to(torch.float32)
|
| 57 |
-
|
| 58 |
-
# upsample_nearest_nhwc fails with large batch sizes. see https://github.com/huggingface/diffusers/issues/984
|
| 59 |
-
if hidden_states.shape[0] >= 64:
|
| 60 |
-
hidden_states = hidden_states.contiguous()
|
| 61 |
-
|
| 62 |
-
# if `output_size` is passed we force the interpolation output
|
| 63 |
-
# size and do not make use of `scale_factor=2`
|
| 64 |
-
if output_size is None:
|
| 65 |
-
hidden_states = F.interpolate(hidden_states, scale_factor=[1.0, 2.0, 2.0], mode="nearest")
|
| 66 |
-
else:
|
| 67 |
-
hidden_states = F.interpolate(hidden_states, size=output_size, mode="nearest")
|
| 68 |
-
|
| 69 |
-
# If the input is bfloat16, we cast back to bfloat16
|
| 70 |
-
if dtype == torch.bfloat16:
|
| 71 |
-
hidden_states = hidden_states.to(dtype)
|
| 72 |
-
|
| 73 |
-
hidden_states = self.conv(hidden_states)
|
| 74 |
-
|
| 75 |
-
return hidden_states
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
class Downsample3D(nn.Module):
|
| 79 |
-
def __init__(self, channels, use_conv=False, out_channels=None, padding=1, name="conv"):
|
| 80 |
-
super().__init__()
|
| 81 |
-
self.channels = channels
|
| 82 |
-
self.out_channels = out_channels or channels
|
| 83 |
-
self.use_conv = use_conv
|
| 84 |
-
self.padding = padding
|
| 85 |
-
stride = 2
|
| 86 |
-
self.name = name
|
| 87 |
-
|
| 88 |
-
if use_conv:
|
| 89 |
-
self.conv = InflatedConv3d(self.channels, self.out_channels, 3, stride=stride, padding=padding)
|
| 90 |
-
else:
|
| 91 |
-
raise NotImplementedError
|
| 92 |
-
|
| 93 |
-
def forward(self, hidden_states):
|
| 94 |
-
assert hidden_states.shape[1] == self.channels
|
| 95 |
-
if self.use_conv and self.padding == 0:
|
| 96 |
-
raise NotImplementedError
|
| 97 |
-
|
| 98 |
-
assert hidden_states.shape[1] == self.channels
|
| 99 |
-
hidden_states = self.conv(hidden_states)
|
| 100 |
-
|
| 101 |
-
return hidden_states
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
class ResnetBlock3D(nn.Module):
|
| 105 |
-
def __init__(
|
| 106 |
-
self,
|
| 107 |
-
*,
|
| 108 |
-
in_channels,
|
| 109 |
-
out_channels=None,
|
| 110 |
-
conv_shortcut=False,
|
| 111 |
-
dropout=0.0,
|
| 112 |
-
temb_channels=512,
|
| 113 |
-
groups=32,
|
| 114 |
-
groups_out=None,
|
| 115 |
-
pre_norm=True,
|
| 116 |
-
eps=1e-6,
|
| 117 |
-
non_linearity="swish",
|
| 118 |
-
time_embedding_norm="default",
|
| 119 |
-
output_scale_factor=1.0,
|
| 120 |
-
use_in_shortcut=None,
|
| 121 |
-
use_inflated_groupnorm=False,
|
| 122 |
-
):
|
| 123 |
-
super().__init__()
|
| 124 |
-
self.pre_norm = pre_norm
|
| 125 |
-
self.pre_norm = True
|
| 126 |
-
self.in_channels = in_channels
|
| 127 |
-
out_channels = in_channels if out_channels is None else out_channels
|
| 128 |
-
self.out_channels = out_channels
|
| 129 |
-
self.use_conv_shortcut = conv_shortcut
|
| 130 |
-
self.time_embedding_norm = time_embedding_norm
|
| 131 |
-
self.output_scale_factor = output_scale_factor
|
| 132 |
-
|
| 133 |
-
if groups_out is None:
|
| 134 |
-
groups_out = groups
|
| 135 |
-
|
| 136 |
-
assert use_inflated_groupnorm != None
|
| 137 |
-
if use_inflated_groupnorm:
|
| 138 |
-
self.norm1 = InflatedGroupNorm(num_groups=groups, num_channels=in_channels, eps=eps, affine=True)
|
| 139 |
-
else:
|
| 140 |
-
self.norm1 = torch.nn.GroupNorm(num_groups=groups, num_channels=in_channels, eps=eps, affine=True)
|
| 141 |
-
|
| 142 |
-
self.conv1 = InflatedConv3d(in_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
| 143 |
-
|
| 144 |
-
if temb_channels is not None:
|
| 145 |
-
if self.time_embedding_norm == "default":
|
| 146 |
-
time_emb_proj_out_channels = out_channels
|
| 147 |
-
elif self.time_embedding_norm == "scale_shift":
|
| 148 |
-
time_emb_proj_out_channels = out_channels * 2
|
| 149 |
-
else:
|
| 150 |
-
raise ValueError(f"unknown time_embedding_norm : {self.time_embedding_norm} ")
|
| 151 |
-
|
| 152 |
-
self.time_emb_proj = torch.nn.Linear(temb_channels, time_emb_proj_out_channels)
|
| 153 |
-
else:
|
| 154 |
-
self.time_emb_proj = None
|
| 155 |
-
|
| 156 |
-
if self.time_embedding_norm == "scale_shift":
|
| 157 |
-
self.double_len_linear = torch.nn.Linear(time_emb_proj_out_channels, 2 * time_emb_proj_out_channels)
|
| 158 |
-
else:
|
| 159 |
-
self.double_len_linear = None
|
| 160 |
-
|
| 161 |
-
if use_inflated_groupnorm:
|
| 162 |
-
self.norm2 = InflatedGroupNorm(num_groups=groups_out, num_channels=out_channels, eps=eps, affine=True)
|
| 163 |
-
else:
|
| 164 |
-
self.norm2 = torch.nn.GroupNorm(num_groups=groups_out, num_channels=out_channels, eps=eps, affine=True)
|
| 165 |
-
|
| 166 |
-
self.dropout = torch.nn.Dropout(dropout)
|
| 167 |
-
self.conv2 = InflatedConv3d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
| 168 |
-
|
| 169 |
-
if non_linearity == "swish":
|
| 170 |
-
self.nonlinearity = lambda x: F.silu(x)
|
| 171 |
-
elif non_linearity == "mish":
|
| 172 |
-
self.nonlinearity = Mish()
|
| 173 |
-
elif non_linearity == "silu":
|
| 174 |
-
self.nonlinearity = nn.SiLU()
|
| 175 |
-
|
| 176 |
-
self.use_in_shortcut = self.in_channels != self.out_channels if use_in_shortcut is None else use_in_shortcut
|
| 177 |
-
|
| 178 |
-
self.conv_shortcut = None
|
| 179 |
-
if self.use_in_shortcut:
|
| 180 |
-
self.conv_shortcut = InflatedConv3d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
|
| 181 |
-
|
| 182 |
-
def forward(self, input_tensor, temb):
|
| 183 |
-
hidden_states = input_tensor
|
| 184 |
-
|
| 185 |
-
hidden_states = self.norm1(hidden_states)
|
| 186 |
-
hidden_states = self.nonlinearity(hidden_states)
|
| 187 |
-
|
| 188 |
-
hidden_states = self.conv1(hidden_states)
|
| 189 |
-
|
| 190 |
-
if temb is not None:
|
| 191 |
-
if temb.dim() == 2:
|
| 192 |
-
# input (1, 1280)
|
| 193 |
-
temb = self.time_emb_proj(self.nonlinearity(temb))
|
| 194 |
-
temb = temb[:, :, None, None, None] # unsqueeze
|
| 195 |
-
else:
|
| 196 |
-
# input (1, 1280, 16)
|
| 197 |
-
temb = temb.permute(0, 2, 1)
|
| 198 |
-
temb = self.time_emb_proj(self.nonlinearity(temb))
|
| 199 |
-
if self.double_len_linear is not None:
|
| 200 |
-
temb = self.double_len_linear(self.nonlinearity(temb))
|
| 201 |
-
temb = temb.permute(0, 2, 1)
|
| 202 |
-
temb = temb[:, :, :, None, None]
|
| 203 |
-
|
| 204 |
-
if temb is not None and self.time_embedding_norm == "default":
|
| 205 |
-
hidden_states = hidden_states + temb
|
| 206 |
-
|
| 207 |
-
hidden_states = self.norm2(hidden_states)
|
| 208 |
-
|
| 209 |
-
if temb is not None and self.time_embedding_norm == "scale_shift":
|
| 210 |
-
scale, shift = torch.chunk(temb, 2, dim=1)
|
| 211 |
-
hidden_states = hidden_states * (1 + scale) + shift
|
| 212 |
-
|
| 213 |
-
hidden_states = self.nonlinearity(hidden_states)
|
| 214 |
-
|
| 215 |
-
hidden_states = self.dropout(hidden_states)
|
| 216 |
-
hidden_states = self.conv2(hidden_states)
|
| 217 |
-
|
| 218 |
-
if self.conv_shortcut is not None:
|
| 219 |
-
input_tensor = self.conv_shortcut(input_tensor)
|
| 220 |
-
|
| 221 |
-
output_tensor = (input_tensor + hidden_states) / self.output_scale_factor
|
| 222 |
-
|
| 223 |
-
return output_tensor
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
class Mish(torch.nn.Module):
|
| 227 |
-
def forward(self, hidden_states):
|
| 228 |
-
return hidden_states * torch.tanh(torch.nn.functional.softplus(hidden_states))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
latentsync/models/stable_syncnet.py
DELETED
|
@@ -1,233 +0,0 @@
|
|
| 1 |
-
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import torch
|
| 16 |
-
from torch import nn
|
| 17 |
-
from einops import rearrange
|
| 18 |
-
from torch.nn import functional as F
|
| 19 |
-
from .attention import Attention
|
| 20 |
-
|
| 21 |
-
import torch.nn as nn
|
| 22 |
-
import torch.nn.functional as F
|
| 23 |
-
|
| 24 |
-
from diffusers.models.attention import FeedForward
|
| 25 |
-
from einops import rearrange
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
class StableSyncNet(nn.Module):
|
| 29 |
-
def __init__(self, config, gradient_checkpointing=False):
|
| 30 |
-
super().__init__()
|
| 31 |
-
self.audio_encoder = DownEncoder2D(
|
| 32 |
-
in_channels=config["audio_encoder"]["in_channels"],
|
| 33 |
-
block_out_channels=config["audio_encoder"]["block_out_channels"],
|
| 34 |
-
downsample_factors=config["audio_encoder"]["downsample_factors"],
|
| 35 |
-
dropout=config["audio_encoder"]["dropout"],
|
| 36 |
-
attn_blocks=config["audio_encoder"]["attn_blocks"],
|
| 37 |
-
gradient_checkpointing=gradient_checkpointing,
|
| 38 |
-
)
|
| 39 |
-
|
| 40 |
-
self.visual_encoder = DownEncoder2D(
|
| 41 |
-
in_channels=config["visual_encoder"]["in_channels"],
|
| 42 |
-
block_out_channels=config["visual_encoder"]["block_out_channels"],
|
| 43 |
-
downsample_factors=config["visual_encoder"]["downsample_factors"],
|
| 44 |
-
dropout=config["visual_encoder"]["dropout"],
|
| 45 |
-
attn_blocks=config["visual_encoder"]["attn_blocks"],
|
| 46 |
-
gradient_checkpointing=gradient_checkpointing,
|
| 47 |
-
)
|
| 48 |
-
|
| 49 |
-
self.eval()
|
| 50 |
-
|
| 51 |
-
def forward(self, image_sequences, audio_sequences):
|
| 52 |
-
vision_embeds = self.visual_encoder(image_sequences) # (b, c, 1, 1)
|
| 53 |
-
audio_embeds = self.audio_encoder(audio_sequences) # (b, c, 1, 1)
|
| 54 |
-
|
| 55 |
-
vision_embeds = vision_embeds.reshape(vision_embeds.shape[0], -1) # (b, c)
|
| 56 |
-
audio_embeds = audio_embeds.reshape(audio_embeds.shape[0], -1) # (b, c)
|
| 57 |
-
|
| 58 |
-
# Make them unit vectors
|
| 59 |
-
vision_embeds = F.normalize(vision_embeds, p=2, dim=1)
|
| 60 |
-
audio_embeds = F.normalize(audio_embeds, p=2, dim=1)
|
| 61 |
-
|
| 62 |
-
return vision_embeds, audio_embeds
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
class ResnetBlock2D(nn.Module):
|
| 66 |
-
def __init__(
|
| 67 |
-
self,
|
| 68 |
-
in_channels: int,
|
| 69 |
-
out_channels: int,
|
| 70 |
-
dropout: float = 0.0,
|
| 71 |
-
norm_num_groups: int = 32,
|
| 72 |
-
eps: float = 1e-6,
|
| 73 |
-
act_fn: str = "silu",
|
| 74 |
-
downsample_factor=2,
|
| 75 |
-
):
|
| 76 |
-
super().__init__()
|
| 77 |
-
|
| 78 |
-
self.norm1 = nn.GroupNorm(num_groups=norm_num_groups, num_channels=in_channels, eps=eps, affine=True)
|
| 79 |
-
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
| 80 |
-
|
| 81 |
-
self.norm2 = nn.GroupNorm(num_groups=norm_num_groups, num_channels=out_channels, eps=eps, affine=True)
|
| 82 |
-
self.dropout = nn.Dropout(dropout)
|
| 83 |
-
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
| 84 |
-
|
| 85 |
-
if act_fn == "relu":
|
| 86 |
-
self.act_fn = nn.ReLU()
|
| 87 |
-
elif act_fn == "silu":
|
| 88 |
-
self.act_fn = nn.SiLU()
|
| 89 |
-
|
| 90 |
-
if in_channels != out_channels:
|
| 91 |
-
self.conv_shortcut = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
|
| 92 |
-
else:
|
| 93 |
-
self.conv_shortcut = None
|
| 94 |
-
|
| 95 |
-
if isinstance(downsample_factor, list):
|
| 96 |
-
downsample_factor = tuple(downsample_factor)
|
| 97 |
-
|
| 98 |
-
if downsample_factor == 1:
|
| 99 |
-
self.downsample_conv = None
|
| 100 |
-
else:
|
| 101 |
-
self.downsample_conv = nn.Conv2d(
|
| 102 |
-
out_channels, out_channels, kernel_size=3, stride=downsample_factor, padding=0
|
| 103 |
-
)
|
| 104 |
-
self.pad = (0, 1, 0, 1)
|
| 105 |
-
if isinstance(downsample_factor, tuple):
|
| 106 |
-
if downsample_factor[0] == 1:
|
| 107 |
-
self.pad = (0, 1, 1, 1) # The padding order is from back to front
|
| 108 |
-
elif downsample_factor[1] == 1:
|
| 109 |
-
self.pad = (1, 1, 0, 1)
|
| 110 |
-
|
| 111 |
-
def forward(self, input_tensor):
|
| 112 |
-
hidden_states = input_tensor
|
| 113 |
-
|
| 114 |
-
hidden_states = self.norm1(hidden_states)
|
| 115 |
-
hidden_states = self.act_fn(hidden_states)
|
| 116 |
-
|
| 117 |
-
hidden_states = self.conv1(hidden_states)
|
| 118 |
-
hidden_states = self.norm2(hidden_states)
|
| 119 |
-
hidden_states = self.act_fn(hidden_states)
|
| 120 |
-
|
| 121 |
-
hidden_states = self.dropout(hidden_states)
|
| 122 |
-
hidden_states = self.conv2(hidden_states)
|
| 123 |
-
|
| 124 |
-
if self.conv_shortcut is not None:
|
| 125 |
-
input_tensor = self.conv_shortcut(input_tensor)
|
| 126 |
-
|
| 127 |
-
hidden_states += input_tensor
|
| 128 |
-
|
| 129 |
-
if self.downsample_conv is not None:
|
| 130 |
-
hidden_states = F.pad(hidden_states, self.pad, mode="constant", value=0)
|
| 131 |
-
hidden_states = self.downsample_conv(hidden_states)
|
| 132 |
-
|
| 133 |
-
return hidden_states
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
class AttentionBlock2D(nn.Module):
|
| 137 |
-
def __init__(self, query_dim, norm_num_groups=32, dropout=0.0):
|
| 138 |
-
super().__init__()
|
| 139 |
-
self.norm1 = torch.nn.GroupNorm(num_groups=norm_num_groups, num_channels=query_dim, eps=1e-6, affine=True)
|
| 140 |
-
self.norm2 = nn.LayerNorm(query_dim)
|
| 141 |
-
self.norm3 = nn.LayerNorm(query_dim)
|
| 142 |
-
|
| 143 |
-
self.ff = FeedForward(query_dim, dropout=dropout, activation_fn="geglu")
|
| 144 |
-
|
| 145 |
-
self.conv_in = nn.Conv2d(query_dim, query_dim, kernel_size=1, stride=1, padding=0)
|
| 146 |
-
self.conv_out = nn.Conv2d(query_dim, query_dim, kernel_size=1, stride=1, padding=0)
|
| 147 |
-
|
| 148 |
-
self.attn = Attention(query_dim=query_dim, heads=8, dim_head=query_dim // 8, dropout=dropout, bias=True)
|
| 149 |
-
|
| 150 |
-
def forward(self, hidden_states):
|
| 151 |
-
assert hidden_states.dim() == 4, f"Expected hidden_states to have ndim=4, but got ndim={hidden_states.dim()}."
|
| 152 |
-
|
| 153 |
-
batch, channel, height, width = hidden_states.shape
|
| 154 |
-
residual = hidden_states
|
| 155 |
-
|
| 156 |
-
hidden_states = self.norm1(hidden_states)
|
| 157 |
-
hidden_states = self.conv_in(hidden_states)
|
| 158 |
-
hidden_states = rearrange(hidden_states, "b c h w -> b (h w) c")
|
| 159 |
-
|
| 160 |
-
norm_hidden_states = self.norm2(hidden_states)
|
| 161 |
-
|
| 162 |
-
hidden_states = self.attn(norm_hidden_states, attention_mask=None) + hidden_states
|
| 163 |
-
hidden_states = self.ff(self.norm3(hidden_states)) + hidden_states
|
| 164 |
-
|
| 165 |
-
hidden_states = rearrange(hidden_states, "b (h w) c -> b c h w", h=height, w=width).contiguous()
|
| 166 |
-
hidden_states = self.conv_out(hidden_states)
|
| 167 |
-
|
| 168 |
-
hidden_states = hidden_states + residual
|
| 169 |
-
return hidden_states
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
class DownEncoder2D(nn.Module):
|
| 173 |
-
def __init__(
|
| 174 |
-
self,
|
| 175 |
-
in_channels=4 * 16,
|
| 176 |
-
block_out_channels=[64, 128, 256, 256],
|
| 177 |
-
downsample_factors=[2, 2, 2, 2],
|
| 178 |
-
layers_per_block=2,
|
| 179 |
-
norm_num_groups=32,
|
| 180 |
-
attn_blocks=[1, 1, 1, 1],
|
| 181 |
-
dropout: float = 0.0,
|
| 182 |
-
act_fn="silu",
|
| 183 |
-
gradient_checkpointing=False,
|
| 184 |
-
):
|
| 185 |
-
super().__init__()
|
| 186 |
-
self.layers_per_block = layers_per_block
|
| 187 |
-
self.gradient_checkpointing = gradient_checkpointing
|
| 188 |
-
|
| 189 |
-
# in
|
| 190 |
-
self.conv_in = nn.Conv2d(in_channels, block_out_channels[0], kernel_size=3, stride=1, padding=1)
|
| 191 |
-
|
| 192 |
-
# down
|
| 193 |
-
self.down_blocks = nn.ModuleList([])
|
| 194 |
-
|
| 195 |
-
output_channels = block_out_channels[0]
|
| 196 |
-
for i, block_out_channel in enumerate(block_out_channels):
|
| 197 |
-
input_channels = output_channels
|
| 198 |
-
output_channels = block_out_channel
|
| 199 |
-
|
| 200 |
-
down_block = ResnetBlock2D(
|
| 201 |
-
in_channels=input_channels,
|
| 202 |
-
out_channels=output_channels,
|
| 203 |
-
downsample_factor=downsample_factors[i],
|
| 204 |
-
norm_num_groups=norm_num_groups,
|
| 205 |
-
dropout=dropout,
|
| 206 |
-
act_fn=act_fn,
|
| 207 |
-
)
|
| 208 |
-
|
| 209 |
-
self.down_blocks.append(down_block)
|
| 210 |
-
|
| 211 |
-
if attn_blocks[i] == 1:
|
| 212 |
-
attention_block = AttentionBlock2D(query_dim=output_channels, dropout=dropout)
|
| 213 |
-
self.down_blocks.append(attention_block)
|
| 214 |
-
|
| 215 |
-
# out
|
| 216 |
-
self.norm_out = nn.GroupNorm(num_channels=block_out_channels[-1], num_groups=norm_num_groups, eps=1e-6)
|
| 217 |
-
self.act_fn_out = nn.ReLU()
|
| 218 |
-
|
| 219 |
-
def forward(self, hidden_states):
|
| 220 |
-
hidden_states = self.conv_in(hidden_states)
|
| 221 |
-
|
| 222 |
-
# down
|
| 223 |
-
for down_block in self.down_blocks:
|
| 224 |
-
if self.gradient_checkpointing:
|
| 225 |
-
hidden_states = torch.utils.checkpoint.checkpoint(down_block, hidden_states, use_reentrant=False)
|
| 226 |
-
else:
|
| 227 |
-
hidden_states = down_block(hidden_states)
|
| 228 |
-
|
| 229 |
-
# post-process
|
| 230 |
-
hidden_states = self.norm_out(hidden_states)
|
| 231 |
-
hidden_states = self.act_fn_out(hidden_states)
|
| 232 |
-
|
| 233 |
-
return hidden_states
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
latentsync/models/unet.py
DELETED
|
@@ -1,512 +0,0 @@
|
|
| 1 |
-
# Adapted from https://github.com/guoyww/AnimateDiff/blob/main/animatediff/models/unet.py
|
| 2 |
-
|
| 3 |
-
from dataclasses import dataclass
|
| 4 |
-
from typing import List, Optional, Tuple, Union
|
| 5 |
-
import copy
|
| 6 |
-
|
| 7 |
-
import torch
|
| 8 |
-
import torch.nn as nn
|
| 9 |
-
import torch.utils.checkpoint
|
| 10 |
-
|
| 11 |
-
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 12 |
-
from diffusers.models import ModelMixin
|
| 13 |
-
|
| 14 |
-
from diffusers.utils import BaseOutput, logging
|
| 15 |
-
from diffusers.models.embeddings import TimestepEmbedding, Timesteps
|
| 16 |
-
from .unet_blocks import (
|
| 17 |
-
CrossAttnDownBlock3D,
|
| 18 |
-
CrossAttnUpBlock3D,
|
| 19 |
-
DownBlock3D,
|
| 20 |
-
UNetMidBlock3DCrossAttn,
|
| 21 |
-
UpBlock3D,
|
| 22 |
-
get_down_block,
|
| 23 |
-
get_up_block,
|
| 24 |
-
)
|
| 25 |
-
from .resnet import InflatedConv3d, InflatedGroupNorm
|
| 26 |
-
|
| 27 |
-
from ..utils.util import zero_rank_log
|
| 28 |
-
from .utils import zero_module
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
@dataclass
|
| 35 |
-
class UNet3DConditionOutput(BaseOutput):
|
| 36 |
-
sample: torch.FloatTensor
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
class UNet3DConditionModel(ModelMixin, ConfigMixin):
|
| 40 |
-
_supports_gradient_checkpointing = True
|
| 41 |
-
|
| 42 |
-
@register_to_config
|
| 43 |
-
def __init__(
|
| 44 |
-
self,
|
| 45 |
-
sample_size: Optional[int] = None,
|
| 46 |
-
in_channels: int = 4,
|
| 47 |
-
out_channels: int = 4,
|
| 48 |
-
center_input_sample: bool = False,
|
| 49 |
-
flip_sin_to_cos: bool = True,
|
| 50 |
-
freq_shift: int = 0,
|
| 51 |
-
down_block_types: Tuple[str] = (
|
| 52 |
-
"CrossAttnDownBlock3D",
|
| 53 |
-
"CrossAttnDownBlock3D",
|
| 54 |
-
"CrossAttnDownBlock3D",
|
| 55 |
-
"DownBlock3D",
|
| 56 |
-
),
|
| 57 |
-
mid_block_type: str = "UNetMidBlock3DCrossAttn",
|
| 58 |
-
up_block_types: Tuple[str] = ("UpBlock3D", "CrossAttnUpBlock3D", "CrossAttnUpBlock3D", "CrossAttnUpBlock3D"),
|
| 59 |
-
only_cross_attention: Union[bool, Tuple[bool]] = False,
|
| 60 |
-
block_out_channels: Tuple[int] = (320, 640, 1280, 1280),
|
| 61 |
-
layers_per_block: int = 2,
|
| 62 |
-
downsample_padding: int = 1,
|
| 63 |
-
mid_block_scale_factor: float = 1,
|
| 64 |
-
act_fn: str = "silu",
|
| 65 |
-
norm_num_groups: int = 32,
|
| 66 |
-
norm_eps: float = 1e-5,
|
| 67 |
-
cross_attention_dim: int = 1280,
|
| 68 |
-
attention_head_dim: Union[int, Tuple[int]] = 8,
|
| 69 |
-
dual_cross_attention: bool = False,
|
| 70 |
-
use_linear_projection: bool = False,
|
| 71 |
-
class_embed_type: Optional[str] = None,
|
| 72 |
-
num_class_embeds: Optional[int] = None,
|
| 73 |
-
upcast_attention: bool = False,
|
| 74 |
-
resnet_time_scale_shift: str = "default",
|
| 75 |
-
use_inflated_groupnorm=False,
|
| 76 |
-
# Additional
|
| 77 |
-
use_motion_module=False,
|
| 78 |
-
motion_module_resolutions=(1, 2, 4, 8),
|
| 79 |
-
motion_module_mid_block=False,
|
| 80 |
-
motion_module_decoder_only=False,
|
| 81 |
-
motion_module_type=None,
|
| 82 |
-
motion_module_kwargs={},
|
| 83 |
-
add_audio_layer=False,
|
| 84 |
-
):
|
| 85 |
-
super().__init__()
|
| 86 |
-
|
| 87 |
-
self.sample_size = sample_size
|
| 88 |
-
time_embed_dim = block_out_channels[0] * 4
|
| 89 |
-
self.use_motion_module = use_motion_module
|
| 90 |
-
self.add_audio_layer = add_audio_layer
|
| 91 |
-
|
| 92 |
-
self.conv_in = zero_module(InflatedConv3d(in_channels, block_out_channels[0], kernel_size=3, padding=(1, 1)))
|
| 93 |
-
|
| 94 |
-
# time
|
| 95 |
-
self.time_proj = Timesteps(block_out_channels[0], flip_sin_to_cos, freq_shift)
|
| 96 |
-
timestep_input_dim = block_out_channels[0]
|
| 97 |
-
|
| 98 |
-
self.time_embedding = TimestepEmbedding(timestep_input_dim, time_embed_dim)
|
| 99 |
-
|
| 100 |
-
# class embedding
|
| 101 |
-
if class_embed_type is None and num_class_embeds is not None:
|
| 102 |
-
self.class_embedding = nn.Embedding(num_class_embeds, time_embed_dim)
|
| 103 |
-
elif class_embed_type == "timestep":
|
| 104 |
-
self.class_embedding = TimestepEmbedding(timestep_input_dim, time_embed_dim)
|
| 105 |
-
elif class_embed_type == "identity":
|
| 106 |
-
self.class_embedding = nn.Identity(time_embed_dim, time_embed_dim)
|
| 107 |
-
else:
|
| 108 |
-
self.class_embedding = None
|
| 109 |
-
|
| 110 |
-
self.down_blocks = nn.ModuleList([])
|
| 111 |
-
self.mid_block = None
|
| 112 |
-
self.up_blocks = nn.ModuleList([])
|
| 113 |
-
|
| 114 |
-
if isinstance(only_cross_attention, bool):
|
| 115 |
-
only_cross_attention = [only_cross_attention] * len(down_block_types)
|
| 116 |
-
|
| 117 |
-
if isinstance(attention_head_dim, int):
|
| 118 |
-
attention_head_dim = (attention_head_dim,) * len(down_block_types)
|
| 119 |
-
|
| 120 |
-
# down
|
| 121 |
-
output_channel = block_out_channels[0]
|
| 122 |
-
for i, down_block_type in enumerate(down_block_types):
|
| 123 |
-
res = 2**i
|
| 124 |
-
input_channel = output_channel
|
| 125 |
-
output_channel = block_out_channels[i]
|
| 126 |
-
is_final_block = i == len(block_out_channels) - 1
|
| 127 |
-
|
| 128 |
-
down_block = get_down_block(
|
| 129 |
-
down_block_type,
|
| 130 |
-
num_layers=layers_per_block,
|
| 131 |
-
in_channels=input_channel,
|
| 132 |
-
out_channels=output_channel,
|
| 133 |
-
temb_channels=time_embed_dim,
|
| 134 |
-
add_downsample=not is_final_block,
|
| 135 |
-
resnet_eps=norm_eps,
|
| 136 |
-
resnet_act_fn=act_fn,
|
| 137 |
-
resnet_groups=norm_num_groups,
|
| 138 |
-
cross_attention_dim=cross_attention_dim,
|
| 139 |
-
attn_num_head_channels=attention_head_dim[i],
|
| 140 |
-
downsample_padding=downsample_padding,
|
| 141 |
-
dual_cross_attention=dual_cross_attention,
|
| 142 |
-
use_linear_projection=use_linear_projection,
|
| 143 |
-
only_cross_attention=only_cross_attention[i],
|
| 144 |
-
upcast_attention=upcast_attention,
|
| 145 |
-
resnet_time_scale_shift=resnet_time_scale_shift,
|
| 146 |
-
use_inflated_groupnorm=use_inflated_groupnorm,
|
| 147 |
-
use_motion_module=use_motion_module
|
| 148 |
-
and (res in motion_module_resolutions)
|
| 149 |
-
and (not motion_module_decoder_only),
|
| 150 |
-
motion_module_type=motion_module_type,
|
| 151 |
-
motion_module_kwargs=motion_module_kwargs,
|
| 152 |
-
add_audio_layer=add_audio_layer,
|
| 153 |
-
)
|
| 154 |
-
self.down_blocks.append(down_block)
|
| 155 |
-
|
| 156 |
-
# mid
|
| 157 |
-
if mid_block_type == "UNetMidBlock3DCrossAttn":
|
| 158 |
-
self.mid_block = UNetMidBlock3DCrossAttn(
|
| 159 |
-
in_channels=block_out_channels[-1],
|
| 160 |
-
temb_channels=time_embed_dim,
|
| 161 |
-
resnet_eps=norm_eps,
|
| 162 |
-
resnet_act_fn=act_fn,
|
| 163 |
-
output_scale_factor=mid_block_scale_factor,
|
| 164 |
-
resnet_time_scale_shift=resnet_time_scale_shift,
|
| 165 |
-
cross_attention_dim=cross_attention_dim,
|
| 166 |
-
attn_num_head_channels=attention_head_dim[-1],
|
| 167 |
-
resnet_groups=norm_num_groups,
|
| 168 |
-
dual_cross_attention=dual_cross_attention,
|
| 169 |
-
use_linear_projection=use_linear_projection,
|
| 170 |
-
upcast_attention=upcast_attention,
|
| 171 |
-
use_inflated_groupnorm=use_inflated_groupnorm,
|
| 172 |
-
use_motion_module=use_motion_module and motion_module_mid_block,
|
| 173 |
-
motion_module_type=motion_module_type,
|
| 174 |
-
motion_module_kwargs=motion_module_kwargs,
|
| 175 |
-
add_audio_layer=add_audio_layer,
|
| 176 |
-
)
|
| 177 |
-
else:
|
| 178 |
-
raise ValueError(f"unknown mid_block_type : {mid_block_type}")
|
| 179 |
-
|
| 180 |
-
# count how many layers upsample the videos
|
| 181 |
-
self.num_upsamplers = 0
|
| 182 |
-
|
| 183 |
-
# up
|
| 184 |
-
reversed_block_out_channels = list(reversed(block_out_channels))
|
| 185 |
-
reversed_attention_head_dim = list(reversed(attention_head_dim))
|
| 186 |
-
only_cross_attention = list(reversed(only_cross_attention))
|
| 187 |
-
output_channel = reversed_block_out_channels[0]
|
| 188 |
-
for i, up_block_type in enumerate(up_block_types):
|
| 189 |
-
res = 2 ** (3 - i)
|
| 190 |
-
is_final_block = i == len(block_out_channels) - 1
|
| 191 |
-
|
| 192 |
-
prev_output_channel = output_channel
|
| 193 |
-
output_channel = reversed_block_out_channels[i]
|
| 194 |
-
input_channel = reversed_block_out_channels[min(i + 1, len(block_out_channels) - 1)]
|
| 195 |
-
|
| 196 |
-
# add upsample block for all BUT final layer
|
| 197 |
-
if not is_final_block:
|
| 198 |
-
add_upsample = True
|
| 199 |
-
self.num_upsamplers += 1
|
| 200 |
-
else:
|
| 201 |
-
add_upsample = False
|
| 202 |
-
|
| 203 |
-
up_block = get_up_block(
|
| 204 |
-
up_block_type,
|
| 205 |
-
num_layers=layers_per_block + 1,
|
| 206 |
-
in_channels=input_channel,
|
| 207 |
-
out_channels=output_channel,
|
| 208 |
-
prev_output_channel=prev_output_channel,
|
| 209 |
-
temb_channels=time_embed_dim,
|
| 210 |
-
add_upsample=add_upsample,
|
| 211 |
-
resnet_eps=norm_eps,
|
| 212 |
-
resnet_act_fn=act_fn,
|
| 213 |
-
resnet_groups=norm_num_groups,
|
| 214 |
-
cross_attention_dim=cross_attention_dim,
|
| 215 |
-
attn_num_head_channels=reversed_attention_head_dim[i],
|
| 216 |
-
dual_cross_attention=dual_cross_attention,
|
| 217 |
-
use_linear_projection=use_linear_projection,
|
| 218 |
-
only_cross_attention=only_cross_attention[i],
|
| 219 |
-
upcast_attention=upcast_attention,
|
| 220 |
-
resnet_time_scale_shift=resnet_time_scale_shift,
|
| 221 |
-
use_inflated_groupnorm=use_inflated_groupnorm,
|
| 222 |
-
use_motion_module=use_motion_module and (res in motion_module_resolutions),
|
| 223 |
-
motion_module_type=motion_module_type,
|
| 224 |
-
motion_module_kwargs=motion_module_kwargs,
|
| 225 |
-
add_audio_layer=add_audio_layer,
|
| 226 |
-
)
|
| 227 |
-
self.up_blocks.append(up_block)
|
| 228 |
-
prev_output_channel = output_channel
|
| 229 |
-
|
| 230 |
-
# out
|
| 231 |
-
if use_inflated_groupnorm:
|
| 232 |
-
self.conv_norm_out = InflatedGroupNorm(
|
| 233 |
-
num_channels=block_out_channels[0], num_groups=norm_num_groups, eps=norm_eps
|
| 234 |
-
)
|
| 235 |
-
else:
|
| 236 |
-
self.conv_norm_out = nn.GroupNorm(
|
| 237 |
-
num_channels=block_out_channels[0], num_groups=norm_num_groups, eps=norm_eps
|
| 238 |
-
)
|
| 239 |
-
self.conv_act = nn.SiLU()
|
| 240 |
-
|
| 241 |
-
self.conv_out = zero_module(InflatedConv3d(block_out_channels[0], out_channels, kernel_size=3, padding=1))
|
| 242 |
-
|
| 243 |
-
def set_attention_slice(self, slice_size):
|
| 244 |
-
r"""
|
| 245 |
-
Enable sliced attention computation.
|
| 246 |
-
|
| 247 |
-
When this option is enabled, the attention module will split the input tensor in slices, to compute attention
|
| 248 |
-
in several steps. This is useful to save some memory in exchange for a small speed decrease.
|
| 249 |
-
|
| 250 |
-
Args:
|
| 251 |
-
slice_size (`str` or `int` or `list(int)`, *optional*, defaults to `"auto"`):
|
| 252 |
-
When `"auto"`, halves the input to the attention heads, so attention will be computed in two steps. If
|
| 253 |
-
`"max"`, maxium amount of memory will be saved by running only one slice at a time. If a number is
|
| 254 |
-
provided, uses as many slices as `attention_head_dim // slice_size`. In this case, `attention_head_dim`
|
| 255 |
-
must be a multiple of `slice_size`.
|
| 256 |
-
"""
|
| 257 |
-
sliceable_head_dims = []
|
| 258 |
-
|
| 259 |
-
def fn_recursive_retrieve_slicable_dims(module: torch.nn.Module):
|
| 260 |
-
if hasattr(module, "set_attention_slice"):
|
| 261 |
-
sliceable_head_dims.append(module.sliceable_head_dim)
|
| 262 |
-
|
| 263 |
-
for child in module.children():
|
| 264 |
-
fn_recursive_retrieve_slicable_dims(child)
|
| 265 |
-
|
| 266 |
-
# retrieve number of attention layers
|
| 267 |
-
for module in self.children():
|
| 268 |
-
fn_recursive_retrieve_slicable_dims(module)
|
| 269 |
-
|
| 270 |
-
num_slicable_layers = len(sliceable_head_dims)
|
| 271 |
-
|
| 272 |
-
if slice_size == "auto":
|
| 273 |
-
# half the attention head size is usually a good trade-off between
|
| 274 |
-
# speed and memory
|
| 275 |
-
slice_size = [dim // 2 for dim in sliceable_head_dims]
|
| 276 |
-
elif slice_size == "max":
|
| 277 |
-
# make smallest slice possible
|
| 278 |
-
slice_size = num_slicable_layers * [1]
|
| 279 |
-
|
| 280 |
-
slice_size = num_slicable_layers * [slice_size] if not isinstance(slice_size, list) else slice_size
|
| 281 |
-
|
| 282 |
-
if len(slice_size) != len(sliceable_head_dims):
|
| 283 |
-
raise ValueError(
|
| 284 |
-
f"You have provided {len(slice_size)}, but {self.config} has {len(sliceable_head_dims)} different"
|
| 285 |
-
f" attention layers. Make sure to match `len(slice_size)` to be {len(sliceable_head_dims)}."
|
| 286 |
-
)
|
| 287 |
-
|
| 288 |
-
for i in range(len(slice_size)):
|
| 289 |
-
size = slice_size[i]
|
| 290 |
-
dim = sliceable_head_dims[i]
|
| 291 |
-
if size is not None and size > dim:
|
| 292 |
-
raise ValueError(f"size {size} has to be smaller or equal to {dim}.")
|
| 293 |
-
|
| 294 |
-
# Recursively walk through all the children.
|
| 295 |
-
# Any children which exposes the set_attention_slice method
|
| 296 |
-
# gets the message
|
| 297 |
-
def fn_recursive_set_attention_slice(module: torch.nn.Module, slice_size: List[int]):
|
| 298 |
-
if hasattr(module, "set_attention_slice"):
|
| 299 |
-
module.set_attention_slice(slice_size.pop())
|
| 300 |
-
|
| 301 |
-
for child in module.children():
|
| 302 |
-
fn_recursive_set_attention_slice(child, slice_size)
|
| 303 |
-
|
| 304 |
-
reversed_slice_size = list(reversed(slice_size))
|
| 305 |
-
for module in self.children():
|
| 306 |
-
fn_recursive_set_attention_slice(module, reversed_slice_size)
|
| 307 |
-
|
| 308 |
-
def _set_gradient_checkpointing(self, module, value=False):
|
| 309 |
-
if isinstance(module, (CrossAttnDownBlock3D, DownBlock3D, CrossAttnUpBlock3D, UpBlock3D)):
|
| 310 |
-
module.gradient_checkpointing = value
|
| 311 |
-
|
| 312 |
-
def forward(
|
| 313 |
-
self,
|
| 314 |
-
sample: torch.FloatTensor,
|
| 315 |
-
timestep: Union[torch.Tensor, float, int],
|
| 316 |
-
encoder_hidden_states: torch.Tensor = None,
|
| 317 |
-
class_labels: Optional[torch.Tensor] = None,
|
| 318 |
-
attention_mask: Optional[torch.Tensor] = None,
|
| 319 |
-
# support controlnet
|
| 320 |
-
down_block_additional_residuals: Optional[Tuple[torch.Tensor]] = None,
|
| 321 |
-
mid_block_additional_residual: Optional[torch.Tensor] = None,
|
| 322 |
-
return_dict: bool = True,
|
| 323 |
-
) -> Union[UNet3DConditionOutput, Tuple]:
|
| 324 |
-
r"""
|
| 325 |
-
Args:
|
| 326 |
-
sample (`torch.FloatTensor`): (batch, channel, height, width) noisy inputs tensor
|
| 327 |
-
timestep (`torch.FloatTensor` or `float` or `int`): (batch) timesteps
|
| 328 |
-
encoder_hidden_states (`torch.FloatTensor`): (batch, sequence_length, feature_dim) encoder hidden states
|
| 329 |
-
return_dict (`bool`, *optional*, defaults to `True`):
|
| 330 |
-
Whether or not to return a [`models.unet_2d_condition.UNet2DConditionOutput`] instead of a plain tuple.
|
| 331 |
-
|
| 332 |
-
Returns:
|
| 333 |
-
[`~models.unet_2d_condition.UNet2DConditionOutput`] or `tuple`:
|
| 334 |
-
[`~models.unet_2d_condition.UNet2DConditionOutput`] if `return_dict` is True, otherwise a `tuple`. When
|
| 335 |
-
returning a tuple, the first element is the sample tensor.
|
| 336 |
-
"""
|
| 337 |
-
# By default samples have to be AT least a multiple of the overall upsampling factor.
|
| 338 |
-
# The overall upsampling factor is equal to 2 ** (# num of upsampling layears).
|
| 339 |
-
# However, the upsampling interpolation output size can be forced to fit any upsampling size
|
| 340 |
-
# on the fly if necessary.
|
| 341 |
-
default_overall_up_factor = 2**self.num_upsamplers
|
| 342 |
-
|
| 343 |
-
# upsample size should be forwarded when sample is not a multiple of `default_overall_up_factor`
|
| 344 |
-
forward_upsample_size = False
|
| 345 |
-
upsample_size = None
|
| 346 |
-
|
| 347 |
-
if any(s % default_overall_up_factor != 0 for s in sample.shape[-2:]):
|
| 348 |
-
logger.info("Forward upsample size to force interpolation output size.")
|
| 349 |
-
forward_upsample_size = True
|
| 350 |
-
|
| 351 |
-
# prepare attention_mask
|
| 352 |
-
if attention_mask is not None:
|
| 353 |
-
attention_mask = (1 - attention_mask.to(sample.dtype)) * -10000.0
|
| 354 |
-
attention_mask = attention_mask.unsqueeze(1)
|
| 355 |
-
|
| 356 |
-
# center input if necessary
|
| 357 |
-
if self.config.center_input_sample:
|
| 358 |
-
sample = 2 * sample - 1.0
|
| 359 |
-
|
| 360 |
-
# time
|
| 361 |
-
timesteps = timestep
|
| 362 |
-
if not torch.is_tensor(timesteps):
|
| 363 |
-
# This would be a good case for the `match` statement (Python 3.10+)
|
| 364 |
-
is_mps = sample.device.type == "mps"
|
| 365 |
-
if isinstance(timestep, float):
|
| 366 |
-
dtype = torch.float32 if is_mps else torch.float64
|
| 367 |
-
else:
|
| 368 |
-
dtype = torch.int32 if is_mps else torch.int64
|
| 369 |
-
timesteps = torch.tensor([timesteps], dtype=dtype, device=sample.device)
|
| 370 |
-
elif len(timesteps.shape) == 0:
|
| 371 |
-
timesteps = timesteps[None].to(sample.device)
|
| 372 |
-
|
| 373 |
-
# broadcast to batch dimension in a way that's compatible with ONNX/Core ML
|
| 374 |
-
timesteps = timesteps.expand(sample.shape[0])
|
| 375 |
-
|
| 376 |
-
t_emb = self.time_proj(timesteps)
|
| 377 |
-
|
| 378 |
-
# timesteps does not contain any weights and will always return f32 tensors
|
| 379 |
-
# but time_embedding might actually be running in fp16. so we need to cast here.
|
| 380 |
-
# there might be better ways to encapsulate this.
|
| 381 |
-
t_emb = t_emb.to(dtype=self.dtype)
|
| 382 |
-
emb = self.time_embedding(t_emb)
|
| 383 |
-
|
| 384 |
-
if self.class_embedding is not None:
|
| 385 |
-
if class_labels is None:
|
| 386 |
-
raise ValueError("class_labels should be provided when num_class_embeds > 0")
|
| 387 |
-
|
| 388 |
-
if self.config.class_embed_type == "timestep":
|
| 389 |
-
class_labels = self.time_proj(class_labels)
|
| 390 |
-
|
| 391 |
-
class_emb = self.class_embedding(class_labels).to(dtype=self.dtype)
|
| 392 |
-
emb = emb + class_emb
|
| 393 |
-
|
| 394 |
-
# pre-process
|
| 395 |
-
sample = self.conv_in(sample)
|
| 396 |
-
|
| 397 |
-
# down
|
| 398 |
-
down_block_res_samples = (sample,)
|
| 399 |
-
for downsample_block in self.down_blocks:
|
| 400 |
-
if hasattr(downsample_block, "has_cross_attention") and downsample_block.has_cross_attention:
|
| 401 |
-
sample, res_samples = downsample_block(
|
| 402 |
-
hidden_states=sample,
|
| 403 |
-
temb=emb,
|
| 404 |
-
encoder_hidden_states=encoder_hidden_states,
|
| 405 |
-
attention_mask=attention_mask,
|
| 406 |
-
)
|
| 407 |
-
else:
|
| 408 |
-
sample, res_samples = downsample_block(
|
| 409 |
-
hidden_states=sample, temb=emb, encoder_hidden_states=encoder_hidden_states
|
| 410 |
-
)
|
| 411 |
-
|
| 412 |
-
down_block_res_samples += res_samples
|
| 413 |
-
|
| 414 |
-
# support controlnet
|
| 415 |
-
down_block_res_samples = list(down_block_res_samples)
|
| 416 |
-
if down_block_additional_residuals is not None:
|
| 417 |
-
for i, down_block_additional_residual in enumerate(down_block_additional_residuals):
|
| 418 |
-
if down_block_additional_residual.dim() == 4: # boardcast
|
| 419 |
-
down_block_additional_residual = down_block_additional_residual.unsqueeze(2)
|
| 420 |
-
down_block_res_samples[i] = down_block_res_samples[i] + down_block_additional_residual
|
| 421 |
-
|
| 422 |
-
# mid
|
| 423 |
-
sample = self.mid_block(
|
| 424 |
-
sample, emb, encoder_hidden_states=encoder_hidden_states, attention_mask=attention_mask
|
| 425 |
-
)
|
| 426 |
-
|
| 427 |
-
# support controlnet
|
| 428 |
-
if mid_block_additional_residual is not None:
|
| 429 |
-
if mid_block_additional_residual.dim() == 4: # boardcast
|
| 430 |
-
mid_block_additional_residual = mid_block_additional_residual.unsqueeze(2)
|
| 431 |
-
sample = sample + mid_block_additional_residual
|
| 432 |
-
|
| 433 |
-
# up
|
| 434 |
-
for i, upsample_block in enumerate(self.up_blocks):
|
| 435 |
-
is_final_block = i == len(self.up_blocks) - 1
|
| 436 |
-
|
| 437 |
-
res_samples = down_block_res_samples[-len(upsample_block.resnets) :]
|
| 438 |
-
down_block_res_samples = down_block_res_samples[: -len(upsample_block.resnets)]
|
| 439 |
-
|
| 440 |
-
# if we have not reached the final block and need to forward the
|
| 441 |
-
# upsample size, we do it here
|
| 442 |
-
if not is_final_block and forward_upsample_size:
|
| 443 |
-
upsample_size = down_block_res_samples[-1].shape[2:]
|
| 444 |
-
|
| 445 |
-
if hasattr(upsample_block, "has_cross_attention") and upsample_block.has_cross_attention:
|
| 446 |
-
sample = upsample_block(
|
| 447 |
-
hidden_states=sample,
|
| 448 |
-
temb=emb,
|
| 449 |
-
res_hidden_states_tuple=res_samples,
|
| 450 |
-
encoder_hidden_states=encoder_hidden_states,
|
| 451 |
-
upsample_size=upsample_size,
|
| 452 |
-
attention_mask=attention_mask,
|
| 453 |
-
)
|
| 454 |
-
else:
|
| 455 |
-
sample = upsample_block(
|
| 456 |
-
hidden_states=sample,
|
| 457 |
-
temb=emb,
|
| 458 |
-
res_hidden_states_tuple=res_samples,
|
| 459 |
-
upsample_size=upsample_size,
|
| 460 |
-
encoder_hidden_states=encoder_hidden_states,
|
| 461 |
-
)
|
| 462 |
-
|
| 463 |
-
# post-process
|
| 464 |
-
sample = self.conv_norm_out(sample)
|
| 465 |
-
sample = self.conv_act(sample)
|
| 466 |
-
sample = self.conv_out(sample)
|
| 467 |
-
|
| 468 |
-
if not return_dict:
|
| 469 |
-
return (sample,)
|
| 470 |
-
|
| 471 |
-
return UNet3DConditionOutput(sample=sample)
|
| 472 |
-
|
| 473 |
-
def load_state_dict(self, state_dict, strict=True):
|
| 474 |
-
# If the loaded checkpoint's in_channels or out_channels are different from config
|
| 475 |
-
if state_dict["conv_in.weight"].shape[1] != self.config.in_channels:
|
| 476 |
-
del state_dict["conv_in.weight"]
|
| 477 |
-
del state_dict["conv_in.bias"]
|
| 478 |
-
if state_dict["conv_out.weight"].shape[0] != self.config.out_channels:
|
| 479 |
-
del state_dict["conv_out.weight"]
|
| 480 |
-
del state_dict["conv_out.bias"]
|
| 481 |
-
|
| 482 |
-
# If the loaded checkpoint's cross_attention_dim is different from config
|
| 483 |
-
keys_to_remove = []
|
| 484 |
-
for key in state_dict:
|
| 485 |
-
if "attn2.to_k." in key or "attn2.to_v." in key:
|
| 486 |
-
if state_dict[key].shape[1] != self.config.cross_attention_dim:
|
| 487 |
-
keys_to_remove.append(key)
|
| 488 |
-
|
| 489 |
-
for key in keys_to_remove:
|
| 490 |
-
del state_dict[key]
|
| 491 |
-
|
| 492 |
-
return super().load_state_dict(state_dict=state_dict, strict=strict)
|
| 493 |
-
|
| 494 |
-
@classmethod
|
| 495 |
-
def from_pretrained(cls, model_config: dict, ckpt_path: str, device="cpu"):
|
| 496 |
-
unet = cls.from_config(model_config).to(device)
|
| 497 |
-
if ckpt_path != "":
|
| 498 |
-
zero_rank_log(logger, f"Load from checkpoint: {ckpt_path}")
|
| 499 |
-
ckpt = torch.load(ckpt_path, map_location=device, weights_only=True)
|
| 500 |
-
if "global_step" in ckpt:
|
| 501 |
-
zero_rank_log(logger, f"resume from global_step: {ckpt['global_step']}")
|
| 502 |
-
resume_global_step = ckpt["global_step"]
|
| 503 |
-
else:
|
| 504 |
-
resume_global_step = 0
|
| 505 |
-
unet.load_state_dict(ckpt["state_dict"], strict=False)
|
| 506 |
-
|
| 507 |
-
del ckpt
|
| 508 |
-
torch.cuda.empty_cache()
|
| 509 |
-
else:
|
| 510 |
-
resume_global_step = 0
|
| 511 |
-
|
| 512 |
-
return unet, resume_global_step
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
latentsync/models/unet_blocks.py
DELETED
|
@@ -1,777 +0,0 @@
|
|
| 1 |
-
# Adapted from https://github.com/guoyww/AnimateDiff/blob/main/animatediff/models/unet_blocks.py
|
| 2 |
-
|
| 3 |
-
import torch
|
| 4 |
-
from torch import nn
|
| 5 |
-
|
| 6 |
-
from .attention import Transformer3DModel
|
| 7 |
-
from .resnet import Downsample3D, ResnetBlock3D, Upsample3D
|
| 8 |
-
from .motion_module import get_motion_module
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
def get_down_block(
|
| 12 |
-
down_block_type,
|
| 13 |
-
num_layers,
|
| 14 |
-
in_channels,
|
| 15 |
-
out_channels,
|
| 16 |
-
temb_channels,
|
| 17 |
-
add_downsample,
|
| 18 |
-
resnet_eps,
|
| 19 |
-
resnet_act_fn,
|
| 20 |
-
attn_num_head_channels,
|
| 21 |
-
resnet_groups=None,
|
| 22 |
-
cross_attention_dim=None,
|
| 23 |
-
downsample_padding=None,
|
| 24 |
-
dual_cross_attention=False,
|
| 25 |
-
use_linear_projection=False,
|
| 26 |
-
only_cross_attention=False,
|
| 27 |
-
upcast_attention=False,
|
| 28 |
-
resnet_time_scale_shift="default",
|
| 29 |
-
use_inflated_groupnorm=False,
|
| 30 |
-
use_motion_module=None,
|
| 31 |
-
motion_module_type=None,
|
| 32 |
-
motion_module_kwargs=None,
|
| 33 |
-
add_audio_layer=False,
|
| 34 |
-
):
|
| 35 |
-
down_block_type = down_block_type[7:] if down_block_type.startswith("UNetRes") else down_block_type
|
| 36 |
-
if down_block_type == "DownBlock3D":
|
| 37 |
-
return DownBlock3D(
|
| 38 |
-
num_layers=num_layers,
|
| 39 |
-
in_channels=in_channels,
|
| 40 |
-
out_channels=out_channels,
|
| 41 |
-
temb_channels=temb_channels,
|
| 42 |
-
add_downsample=add_downsample,
|
| 43 |
-
resnet_eps=resnet_eps,
|
| 44 |
-
resnet_act_fn=resnet_act_fn,
|
| 45 |
-
resnet_groups=resnet_groups,
|
| 46 |
-
downsample_padding=downsample_padding,
|
| 47 |
-
resnet_time_scale_shift=resnet_time_scale_shift,
|
| 48 |
-
use_inflated_groupnorm=use_inflated_groupnorm,
|
| 49 |
-
use_motion_module=use_motion_module,
|
| 50 |
-
motion_module_type=motion_module_type,
|
| 51 |
-
motion_module_kwargs=motion_module_kwargs,
|
| 52 |
-
)
|
| 53 |
-
elif down_block_type == "CrossAttnDownBlock3D":
|
| 54 |
-
if cross_attention_dim is None:
|
| 55 |
-
raise ValueError("cross_attention_dim must be specified for CrossAttnDownBlock3D")
|
| 56 |
-
return CrossAttnDownBlock3D(
|
| 57 |
-
num_layers=num_layers,
|
| 58 |
-
in_channels=in_channels,
|
| 59 |
-
out_channels=out_channels,
|
| 60 |
-
temb_channels=temb_channels,
|
| 61 |
-
add_downsample=add_downsample,
|
| 62 |
-
resnet_eps=resnet_eps,
|
| 63 |
-
resnet_act_fn=resnet_act_fn,
|
| 64 |
-
resnet_groups=resnet_groups,
|
| 65 |
-
downsample_padding=downsample_padding,
|
| 66 |
-
cross_attention_dim=cross_attention_dim,
|
| 67 |
-
attn_num_head_channels=attn_num_head_channels,
|
| 68 |
-
dual_cross_attention=dual_cross_attention,
|
| 69 |
-
use_linear_projection=use_linear_projection,
|
| 70 |
-
only_cross_attention=only_cross_attention,
|
| 71 |
-
upcast_attention=upcast_attention,
|
| 72 |
-
resnet_time_scale_shift=resnet_time_scale_shift,
|
| 73 |
-
use_inflated_groupnorm=use_inflated_groupnorm,
|
| 74 |
-
use_motion_module=use_motion_module,
|
| 75 |
-
motion_module_type=motion_module_type,
|
| 76 |
-
motion_module_kwargs=motion_module_kwargs,
|
| 77 |
-
add_audio_layer=add_audio_layer,
|
| 78 |
-
)
|
| 79 |
-
raise ValueError(f"{down_block_type} does not exist.")
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
def get_up_block(
|
| 83 |
-
up_block_type,
|
| 84 |
-
num_layers,
|
| 85 |
-
in_channels,
|
| 86 |
-
out_channels,
|
| 87 |
-
prev_output_channel,
|
| 88 |
-
temb_channels,
|
| 89 |
-
add_upsample,
|
| 90 |
-
resnet_eps,
|
| 91 |
-
resnet_act_fn,
|
| 92 |
-
attn_num_head_channels,
|
| 93 |
-
resnet_groups=None,
|
| 94 |
-
cross_attention_dim=None,
|
| 95 |
-
dual_cross_attention=False,
|
| 96 |
-
use_linear_projection=False,
|
| 97 |
-
only_cross_attention=False,
|
| 98 |
-
upcast_attention=False,
|
| 99 |
-
resnet_time_scale_shift="default",
|
| 100 |
-
use_inflated_groupnorm=False,
|
| 101 |
-
use_motion_module=None,
|
| 102 |
-
motion_module_type=None,
|
| 103 |
-
motion_module_kwargs=None,
|
| 104 |
-
add_audio_layer=False,
|
| 105 |
-
):
|
| 106 |
-
up_block_type = up_block_type[7:] if up_block_type.startswith("UNetRes") else up_block_type
|
| 107 |
-
if up_block_type == "UpBlock3D":
|
| 108 |
-
return UpBlock3D(
|
| 109 |
-
num_layers=num_layers,
|
| 110 |
-
in_channels=in_channels,
|
| 111 |
-
out_channels=out_channels,
|
| 112 |
-
prev_output_channel=prev_output_channel,
|
| 113 |
-
temb_channels=temb_channels,
|
| 114 |
-
add_upsample=add_upsample,
|
| 115 |
-
resnet_eps=resnet_eps,
|
| 116 |
-
resnet_act_fn=resnet_act_fn,
|
| 117 |
-
resnet_groups=resnet_groups,
|
| 118 |
-
resnet_time_scale_shift=resnet_time_scale_shift,
|
| 119 |
-
use_inflated_groupnorm=use_inflated_groupnorm,
|
| 120 |
-
use_motion_module=use_motion_module,
|
| 121 |
-
motion_module_type=motion_module_type,
|
| 122 |
-
motion_module_kwargs=motion_module_kwargs,
|
| 123 |
-
)
|
| 124 |
-
elif up_block_type == "CrossAttnUpBlock3D":
|
| 125 |
-
if cross_attention_dim is None:
|
| 126 |
-
raise ValueError("cross_attention_dim must be specified for CrossAttnUpBlock3D")
|
| 127 |
-
return CrossAttnUpBlock3D(
|
| 128 |
-
num_layers=num_layers,
|
| 129 |
-
in_channels=in_channels,
|
| 130 |
-
out_channels=out_channels,
|
| 131 |
-
prev_output_channel=prev_output_channel,
|
| 132 |
-
temb_channels=temb_channels,
|
| 133 |
-
add_upsample=add_upsample,
|
| 134 |
-
resnet_eps=resnet_eps,
|
| 135 |
-
resnet_act_fn=resnet_act_fn,
|
| 136 |
-
resnet_groups=resnet_groups,
|
| 137 |
-
cross_attention_dim=cross_attention_dim,
|
| 138 |
-
attn_num_head_channels=attn_num_head_channels,
|
| 139 |
-
dual_cross_attention=dual_cross_attention,
|
| 140 |
-
use_linear_projection=use_linear_projection,
|
| 141 |
-
only_cross_attention=only_cross_attention,
|
| 142 |
-
upcast_attention=upcast_attention,
|
| 143 |
-
resnet_time_scale_shift=resnet_time_scale_shift,
|
| 144 |
-
use_inflated_groupnorm=use_inflated_groupnorm,
|
| 145 |
-
use_motion_module=use_motion_module,
|
| 146 |
-
motion_module_type=motion_module_type,
|
| 147 |
-
motion_module_kwargs=motion_module_kwargs,
|
| 148 |
-
add_audio_layer=add_audio_layer,
|
| 149 |
-
)
|
| 150 |
-
raise ValueError(f"{up_block_type} does not exist.")
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
class UNetMidBlock3DCrossAttn(nn.Module):
|
| 154 |
-
def __init__(
|
| 155 |
-
self,
|
| 156 |
-
in_channels: int,
|
| 157 |
-
temb_channels: int,
|
| 158 |
-
dropout: float = 0.0,
|
| 159 |
-
num_layers: int = 1,
|
| 160 |
-
resnet_eps: float = 1e-6,
|
| 161 |
-
resnet_time_scale_shift: str = "default",
|
| 162 |
-
resnet_act_fn: str = "swish",
|
| 163 |
-
resnet_groups: int = 32,
|
| 164 |
-
resnet_pre_norm: bool = True,
|
| 165 |
-
attn_num_head_channels=1,
|
| 166 |
-
output_scale_factor=1.0,
|
| 167 |
-
cross_attention_dim=1280,
|
| 168 |
-
dual_cross_attention=False,
|
| 169 |
-
use_linear_projection=False,
|
| 170 |
-
upcast_attention=False,
|
| 171 |
-
use_inflated_groupnorm=False,
|
| 172 |
-
use_motion_module=None,
|
| 173 |
-
motion_module_type=None,
|
| 174 |
-
motion_module_kwargs=None,
|
| 175 |
-
add_audio_layer=False,
|
| 176 |
-
):
|
| 177 |
-
super().__init__()
|
| 178 |
-
|
| 179 |
-
self.has_cross_attention = True
|
| 180 |
-
self.attn_num_head_channels = attn_num_head_channels
|
| 181 |
-
resnet_groups = resnet_groups if resnet_groups is not None else min(in_channels // 4, 32)
|
| 182 |
-
|
| 183 |
-
# there is always at least one resnet
|
| 184 |
-
resnets = [
|
| 185 |
-
ResnetBlock3D(
|
| 186 |
-
in_channels=in_channels,
|
| 187 |
-
out_channels=in_channels,
|
| 188 |
-
temb_channels=temb_channels,
|
| 189 |
-
eps=resnet_eps,
|
| 190 |
-
groups=resnet_groups,
|
| 191 |
-
dropout=dropout,
|
| 192 |
-
time_embedding_norm=resnet_time_scale_shift,
|
| 193 |
-
non_linearity=resnet_act_fn,
|
| 194 |
-
output_scale_factor=output_scale_factor,
|
| 195 |
-
pre_norm=resnet_pre_norm,
|
| 196 |
-
use_inflated_groupnorm=use_inflated_groupnorm,
|
| 197 |
-
)
|
| 198 |
-
]
|
| 199 |
-
attentions = []
|
| 200 |
-
motion_modules = []
|
| 201 |
-
|
| 202 |
-
for _ in range(num_layers):
|
| 203 |
-
if dual_cross_attention:
|
| 204 |
-
raise NotImplementedError
|
| 205 |
-
attentions.append(
|
| 206 |
-
Transformer3DModel(
|
| 207 |
-
attn_num_head_channels,
|
| 208 |
-
in_channels // attn_num_head_channels,
|
| 209 |
-
in_channels=in_channels,
|
| 210 |
-
num_layers=1,
|
| 211 |
-
cross_attention_dim=cross_attention_dim,
|
| 212 |
-
norm_num_groups=resnet_groups,
|
| 213 |
-
use_linear_projection=use_linear_projection,
|
| 214 |
-
upcast_attention=upcast_attention,
|
| 215 |
-
add_audio_layer=add_audio_layer,
|
| 216 |
-
)
|
| 217 |
-
)
|
| 218 |
-
motion_modules.append(
|
| 219 |
-
get_motion_module(
|
| 220 |
-
in_channels=in_channels,
|
| 221 |
-
motion_module_type=motion_module_type,
|
| 222 |
-
motion_module_kwargs=motion_module_kwargs,
|
| 223 |
-
)
|
| 224 |
-
if use_motion_module
|
| 225 |
-
else None
|
| 226 |
-
)
|
| 227 |
-
resnets.append(
|
| 228 |
-
ResnetBlock3D(
|
| 229 |
-
in_channels=in_channels,
|
| 230 |
-
out_channels=in_channels,
|
| 231 |
-
temb_channels=temb_channels,
|
| 232 |
-
eps=resnet_eps,
|
| 233 |
-
groups=resnet_groups,
|
| 234 |
-
dropout=dropout,
|
| 235 |
-
time_embedding_norm=resnet_time_scale_shift,
|
| 236 |
-
non_linearity=resnet_act_fn,
|
| 237 |
-
output_scale_factor=output_scale_factor,
|
| 238 |
-
pre_norm=resnet_pre_norm,
|
| 239 |
-
use_inflated_groupnorm=use_inflated_groupnorm,
|
| 240 |
-
)
|
| 241 |
-
)
|
| 242 |
-
|
| 243 |
-
self.attentions = nn.ModuleList(attentions)
|
| 244 |
-
self.resnets = nn.ModuleList(resnets)
|
| 245 |
-
self.motion_modules = nn.ModuleList(motion_modules)
|
| 246 |
-
|
| 247 |
-
def forward(self, hidden_states, temb=None, encoder_hidden_states=None, attention_mask=None):
|
| 248 |
-
hidden_states = self.resnets[0](hidden_states, temb)
|
| 249 |
-
for attn, resnet, motion_module in zip(self.attentions, self.resnets[1:], self.motion_modules):
|
| 250 |
-
hidden_states = attn(
|
| 251 |
-
hidden_states,
|
| 252 |
-
encoder_hidden_states=encoder_hidden_states,
|
| 253 |
-
return_dict=False,
|
| 254 |
-
)[0]
|
| 255 |
-
|
| 256 |
-
if motion_module is not None:
|
| 257 |
-
hidden_states = motion_module(hidden_states, temb, encoder_hidden_states=encoder_hidden_states)
|
| 258 |
-
hidden_states = resnet(hidden_states, temb)
|
| 259 |
-
|
| 260 |
-
return hidden_states
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
class CrossAttnDownBlock3D(nn.Module):
|
| 264 |
-
def __init__(
|
| 265 |
-
self,
|
| 266 |
-
in_channels: int,
|
| 267 |
-
out_channels: int,
|
| 268 |
-
temb_channels: int,
|
| 269 |
-
dropout: float = 0.0,
|
| 270 |
-
num_layers: int = 1,
|
| 271 |
-
resnet_eps: float = 1e-6,
|
| 272 |
-
resnet_time_scale_shift: str = "default",
|
| 273 |
-
resnet_act_fn: str = "swish",
|
| 274 |
-
resnet_groups: int = 32,
|
| 275 |
-
resnet_pre_norm: bool = True,
|
| 276 |
-
attn_num_head_channels=1,
|
| 277 |
-
cross_attention_dim=1280,
|
| 278 |
-
output_scale_factor=1.0,
|
| 279 |
-
downsample_padding=1,
|
| 280 |
-
add_downsample=True,
|
| 281 |
-
dual_cross_attention=False,
|
| 282 |
-
use_linear_projection=False,
|
| 283 |
-
only_cross_attention=False,
|
| 284 |
-
upcast_attention=False,
|
| 285 |
-
use_inflated_groupnorm=False,
|
| 286 |
-
use_motion_module=None,
|
| 287 |
-
motion_module_type=None,
|
| 288 |
-
motion_module_kwargs=None,
|
| 289 |
-
add_audio_layer=False,
|
| 290 |
-
):
|
| 291 |
-
super().__init__()
|
| 292 |
-
resnets = []
|
| 293 |
-
attentions = []
|
| 294 |
-
motion_modules = []
|
| 295 |
-
|
| 296 |
-
self.has_cross_attention = True
|
| 297 |
-
self.attn_num_head_channels = attn_num_head_channels
|
| 298 |
-
|
| 299 |
-
for i in range(num_layers):
|
| 300 |
-
in_channels = in_channels if i == 0 else out_channels
|
| 301 |
-
resnets.append(
|
| 302 |
-
ResnetBlock3D(
|
| 303 |
-
in_channels=in_channels,
|
| 304 |
-
out_channels=out_channels,
|
| 305 |
-
temb_channels=temb_channels,
|
| 306 |
-
eps=resnet_eps,
|
| 307 |
-
groups=resnet_groups,
|
| 308 |
-
dropout=dropout,
|
| 309 |
-
time_embedding_norm=resnet_time_scale_shift,
|
| 310 |
-
non_linearity=resnet_act_fn,
|
| 311 |
-
output_scale_factor=output_scale_factor,
|
| 312 |
-
pre_norm=resnet_pre_norm,
|
| 313 |
-
use_inflated_groupnorm=use_inflated_groupnorm,
|
| 314 |
-
)
|
| 315 |
-
)
|
| 316 |
-
if dual_cross_attention:
|
| 317 |
-
raise NotImplementedError
|
| 318 |
-
attentions.append(
|
| 319 |
-
Transformer3DModel(
|
| 320 |
-
attn_num_head_channels,
|
| 321 |
-
out_channels // attn_num_head_channels,
|
| 322 |
-
in_channels=out_channels,
|
| 323 |
-
num_layers=1,
|
| 324 |
-
cross_attention_dim=cross_attention_dim,
|
| 325 |
-
norm_num_groups=resnet_groups,
|
| 326 |
-
use_linear_projection=use_linear_projection,
|
| 327 |
-
only_cross_attention=only_cross_attention,
|
| 328 |
-
upcast_attention=upcast_attention,
|
| 329 |
-
add_audio_layer=add_audio_layer,
|
| 330 |
-
)
|
| 331 |
-
)
|
| 332 |
-
motion_modules.append(
|
| 333 |
-
get_motion_module(
|
| 334 |
-
in_channels=out_channels,
|
| 335 |
-
motion_module_type=motion_module_type,
|
| 336 |
-
motion_module_kwargs=motion_module_kwargs,
|
| 337 |
-
)
|
| 338 |
-
if use_motion_module
|
| 339 |
-
else None
|
| 340 |
-
)
|
| 341 |
-
|
| 342 |
-
self.attentions = nn.ModuleList(attentions)
|
| 343 |
-
self.resnets = nn.ModuleList(resnets)
|
| 344 |
-
self.motion_modules = nn.ModuleList(motion_modules)
|
| 345 |
-
|
| 346 |
-
if add_downsample:
|
| 347 |
-
self.downsamplers = nn.ModuleList(
|
| 348 |
-
[
|
| 349 |
-
Downsample3D(
|
| 350 |
-
out_channels, use_conv=True, out_channels=out_channels, padding=downsample_padding, name="op"
|
| 351 |
-
)
|
| 352 |
-
]
|
| 353 |
-
)
|
| 354 |
-
else:
|
| 355 |
-
self.downsamplers = None
|
| 356 |
-
|
| 357 |
-
self.gradient_checkpointing = False
|
| 358 |
-
|
| 359 |
-
def forward(self, hidden_states, temb=None, encoder_hidden_states=None, attention_mask=None):
|
| 360 |
-
output_states = ()
|
| 361 |
-
|
| 362 |
-
for resnet, attn, motion_module in zip(self.resnets, self.attentions, self.motion_modules):
|
| 363 |
-
if torch.is_grad_enabled() and self.gradient_checkpointing:
|
| 364 |
-
|
| 365 |
-
def create_custom_forward(module, return_dict=None):
|
| 366 |
-
def custom_forward(*inputs):
|
| 367 |
-
if return_dict is not None:
|
| 368 |
-
return module(*inputs, return_dict=return_dict)
|
| 369 |
-
else:
|
| 370 |
-
return module(*inputs)
|
| 371 |
-
|
| 372 |
-
return custom_forward
|
| 373 |
-
|
| 374 |
-
hidden_states = torch.utils.checkpoint.checkpoint(
|
| 375 |
-
create_custom_forward(resnet), hidden_states, temb, use_reentrant=False
|
| 376 |
-
)
|
| 377 |
-
hidden_states = torch.utils.checkpoint.checkpoint(
|
| 378 |
-
create_custom_forward(attn, return_dict=False),
|
| 379 |
-
hidden_states,
|
| 380 |
-
encoder_hidden_states,
|
| 381 |
-
use_reentrant=False,
|
| 382 |
-
)[0]
|
| 383 |
-
|
| 384 |
-
if motion_module is not None:
|
| 385 |
-
hidden_states = torch.utils.checkpoint.checkpoint(
|
| 386 |
-
create_custom_forward(motion_module),
|
| 387 |
-
hidden_states,
|
| 388 |
-
temb,
|
| 389 |
-
encoder_hidden_states,
|
| 390 |
-
use_reentrant=False,
|
| 391 |
-
)
|
| 392 |
-
else:
|
| 393 |
-
hidden_states = resnet(hidden_states, temb)
|
| 394 |
-
hidden_states = attn(hidden_states, encoder_hidden_states=encoder_hidden_states).sample
|
| 395 |
-
|
| 396 |
-
if motion_module is not None:
|
| 397 |
-
hidden_states = motion_module(hidden_states, temb, encoder_hidden_states=encoder_hidden_states)
|
| 398 |
-
|
| 399 |
-
output_states += (hidden_states,)
|
| 400 |
-
|
| 401 |
-
if self.downsamplers is not None:
|
| 402 |
-
for downsampler in self.downsamplers:
|
| 403 |
-
hidden_states = downsampler(hidden_states)
|
| 404 |
-
|
| 405 |
-
output_states += (hidden_states,)
|
| 406 |
-
|
| 407 |
-
return hidden_states, output_states
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
class DownBlock3D(nn.Module):
|
| 411 |
-
def __init__(
|
| 412 |
-
self,
|
| 413 |
-
in_channels: int,
|
| 414 |
-
out_channels: int,
|
| 415 |
-
temb_channels: int,
|
| 416 |
-
dropout: float = 0.0,
|
| 417 |
-
num_layers: int = 1,
|
| 418 |
-
resnet_eps: float = 1e-6,
|
| 419 |
-
resnet_time_scale_shift: str = "default",
|
| 420 |
-
resnet_act_fn: str = "swish",
|
| 421 |
-
resnet_groups: int = 32,
|
| 422 |
-
resnet_pre_norm: bool = True,
|
| 423 |
-
output_scale_factor=1.0,
|
| 424 |
-
add_downsample=True,
|
| 425 |
-
downsample_padding=1,
|
| 426 |
-
use_inflated_groupnorm=False,
|
| 427 |
-
use_motion_module=None,
|
| 428 |
-
motion_module_type=None,
|
| 429 |
-
motion_module_kwargs=None,
|
| 430 |
-
):
|
| 431 |
-
super().__init__()
|
| 432 |
-
resnets = []
|
| 433 |
-
motion_modules = []
|
| 434 |
-
|
| 435 |
-
for i in range(num_layers):
|
| 436 |
-
in_channels = in_channels if i == 0 else out_channels
|
| 437 |
-
resnets.append(
|
| 438 |
-
ResnetBlock3D(
|
| 439 |
-
in_channels=in_channels,
|
| 440 |
-
out_channels=out_channels,
|
| 441 |
-
temb_channels=temb_channels,
|
| 442 |
-
eps=resnet_eps,
|
| 443 |
-
groups=resnet_groups,
|
| 444 |
-
dropout=dropout,
|
| 445 |
-
time_embedding_norm=resnet_time_scale_shift,
|
| 446 |
-
non_linearity=resnet_act_fn,
|
| 447 |
-
output_scale_factor=output_scale_factor,
|
| 448 |
-
pre_norm=resnet_pre_norm,
|
| 449 |
-
use_inflated_groupnorm=use_inflated_groupnorm,
|
| 450 |
-
)
|
| 451 |
-
)
|
| 452 |
-
motion_modules.append(
|
| 453 |
-
get_motion_module(
|
| 454 |
-
in_channels=out_channels,
|
| 455 |
-
motion_module_type=motion_module_type,
|
| 456 |
-
motion_module_kwargs=motion_module_kwargs,
|
| 457 |
-
)
|
| 458 |
-
if use_motion_module
|
| 459 |
-
else None
|
| 460 |
-
)
|
| 461 |
-
|
| 462 |
-
self.resnets = nn.ModuleList(resnets)
|
| 463 |
-
self.motion_modules = nn.ModuleList(motion_modules)
|
| 464 |
-
|
| 465 |
-
if add_downsample:
|
| 466 |
-
self.downsamplers = nn.ModuleList(
|
| 467 |
-
[
|
| 468 |
-
Downsample3D(
|
| 469 |
-
out_channels, use_conv=True, out_channels=out_channels, padding=downsample_padding, name="op"
|
| 470 |
-
)
|
| 471 |
-
]
|
| 472 |
-
)
|
| 473 |
-
else:
|
| 474 |
-
self.downsamplers = None
|
| 475 |
-
|
| 476 |
-
self.gradient_checkpointing = False
|
| 477 |
-
|
| 478 |
-
def forward(self, hidden_states, temb=None, encoder_hidden_states=None):
|
| 479 |
-
output_states = ()
|
| 480 |
-
|
| 481 |
-
for resnet, motion_module in zip(self.resnets, self.motion_modules):
|
| 482 |
-
if torch.is_grad_enabled() and self.gradient_checkpointing:
|
| 483 |
-
|
| 484 |
-
def create_custom_forward(module):
|
| 485 |
-
def custom_forward(*inputs):
|
| 486 |
-
return module(*inputs)
|
| 487 |
-
|
| 488 |
-
return custom_forward
|
| 489 |
-
|
| 490 |
-
hidden_states = torch.utils.checkpoint.checkpoint(
|
| 491 |
-
create_custom_forward(resnet), hidden_states, temb, use_reentrant=False
|
| 492 |
-
)
|
| 493 |
-
|
| 494 |
-
if motion_module is not None:
|
| 495 |
-
hidden_states = torch.utils.checkpoint.checkpoint(
|
| 496 |
-
create_custom_forward(motion_module),
|
| 497 |
-
hidden_states,
|
| 498 |
-
temb,
|
| 499 |
-
encoder_hidden_states,
|
| 500 |
-
use_reentrant=False,
|
| 501 |
-
)
|
| 502 |
-
else:
|
| 503 |
-
hidden_states = resnet(hidden_states, temb)
|
| 504 |
-
|
| 505 |
-
if motion_module is not None:
|
| 506 |
-
hidden_states = motion_module(hidden_states, temb, encoder_hidden_states=encoder_hidden_states)
|
| 507 |
-
|
| 508 |
-
output_states += (hidden_states,)
|
| 509 |
-
|
| 510 |
-
if self.downsamplers is not None:
|
| 511 |
-
for downsampler in self.downsamplers:
|
| 512 |
-
hidden_states = downsampler(hidden_states)
|
| 513 |
-
|
| 514 |
-
output_states += (hidden_states,)
|
| 515 |
-
|
| 516 |
-
return hidden_states, output_states
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
class CrossAttnUpBlock3D(nn.Module):
|
| 520 |
-
def __init__(
|
| 521 |
-
self,
|
| 522 |
-
in_channels: int,
|
| 523 |
-
out_channels: int,
|
| 524 |
-
prev_output_channel: int,
|
| 525 |
-
temb_channels: int,
|
| 526 |
-
dropout: float = 0.0,
|
| 527 |
-
num_layers: int = 1,
|
| 528 |
-
resnet_eps: float = 1e-6,
|
| 529 |
-
resnet_time_scale_shift: str = "default",
|
| 530 |
-
resnet_act_fn: str = "swish",
|
| 531 |
-
resnet_groups: int = 32,
|
| 532 |
-
resnet_pre_norm: bool = True,
|
| 533 |
-
attn_num_head_channels=1,
|
| 534 |
-
cross_attention_dim=1280,
|
| 535 |
-
output_scale_factor=1.0,
|
| 536 |
-
add_upsample=True,
|
| 537 |
-
dual_cross_attention=False,
|
| 538 |
-
use_linear_projection=False,
|
| 539 |
-
only_cross_attention=False,
|
| 540 |
-
upcast_attention=False,
|
| 541 |
-
use_inflated_groupnorm=False,
|
| 542 |
-
use_motion_module=None,
|
| 543 |
-
motion_module_type=None,
|
| 544 |
-
motion_module_kwargs=None,
|
| 545 |
-
add_audio_layer=False,
|
| 546 |
-
):
|
| 547 |
-
super().__init__()
|
| 548 |
-
resnets = []
|
| 549 |
-
attentions = []
|
| 550 |
-
motion_modules = []
|
| 551 |
-
|
| 552 |
-
self.has_cross_attention = True
|
| 553 |
-
self.attn_num_head_channels = attn_num_head_channels
|
| 554 |
-
|
| 555 |
-
for i in range(num_layers):
|
| 556 |
-
res_skip_channels = in_channels if (i == num_layers - 1) else out_channels
|
| 557 |
-
resnet_in_channels = prev_output_channel if i == 0 else out_channels
|
| 558 |
-
|
| 559 |
-
resnets.append(
|
| 560 |
-
ResnetBlock3D(
|
| 561 |
-
in_channels=resnet_in_channels + res_skip_channels,
|
| 562 |
-
out_channels=out_channels,
|
| 563 |
-
temb_channels=temb_channels,
|
| 564 |
-
eps=resnet_eps,
|
| 565 |
-
groups=resnet_groups,
|
| 566 |
-
dropout=dropout,
|
| 567 |
-
time_embedding_norm=resnet_time_scale_shift,
|
| 568 |
-
non_linearity=resnet_act_fn,
|
| 569 |
-
output_scale_factor=output_scale_factor,
|
| 570 |
-
pre_norm=resnet_pre_norm,
|
| 571 |
-
use_inflated_groupnorm=use_inflated_groupnorm,
|
| 572 |
-
)
|
| 573 |
-
)
|
| 574 |
-
if dual_cross_attention:
|
| 575 |
-
raise NotImplementedError
|
| 576 |
-
attentions.append(
|
| 577 |
-
Transformer3DModel(
|
| 578 |
-
attn_num_head_channels,
|
| 579 |
-
out_channels // attn_num_head_channels,
|
| 580 |
-
in_channels=out_channels,
|
| 581 |
-
num_layers=1,
|
| 582 |
-
cross_attention_dim=cross_attention_dim,
|
| 583 |
-
norm_num_groups=resnet_groups,
|
| 584 |
-
use_linear_projection=use_linear_projection,
|
| 585 |
-
only_cross_attention=only_cross_attention,
|
| 586 |
-
upcast_attention=upcast_attention,
|
| 587 |
-
add_audio_layer=add_audio_layer,
|
| 588 |
-
)
|
| 589 |
-
)
|
| 590 |
-
motion_modules.append(
|
| 591 |
-
get_motion_module(
|
| 592 |
-
in_channels=out_channels,
|
| 593 |
-
motion_module_type=motion_module_type,
|
| 594 |
-
motion_module_kwargs=motion_module_kwargs,
|
| 595 |
-
)
|
| 596 |
-
if use_motion_module
|
| 597 |
-
else None
|
| 598 |
-
)
|
| 599 |
-
|
| 600 |
-
self.attentions = nn.ModuleList(attentions)
|
| 601 |
-
self.resnets = nn.ModuleList(resnets)
|
| 602 |
-
self.motion_modules = nn.ModuleList(motion_modules)
|
| 603 |
-
|
| 604 |
-
if add_upsample:
|
| 605 |
-
self.upsamplers = nn.ModuleList([Upsample3D(out_channels, use_conv=True, out_channels=out_channels)])
|
| 606 |
-
else:
|
| 607 |
-
self.upsamplers = None
|
| 608 |
-
|
| 609 |
-
self.gradient_checkpointing = False
|
| 610 |
-
|
| 611 |
-
def forward(
|
| 612 |
-
self,
|
| 613 |
-
hidden_states,
|
| 614 |
-
res_hidden_states_tuple,
|
| 615 |
-
temb=None,
|
| 616 |
-
encoder_hidden_states=None,
|
| 617 |
-
upsample_size=None,
|
| 618 |
-
attention_mask=None,
|
| 619 |
-
):
|
| 620 |
-
for resnet, attn, motion_module in zip(self.resnets, self.attentions, self.motion_modules):
|
| 621 |
-
# pop res hidden states
|
| 622 |
-
res_hidden_states = res_hidden_states_tuple[-1]
|
| 623 |
-
res_hidden_states_tuple = res_hidden_states_tuple[:-1]
|
| 624 |
-
hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1)
|
| 625 |
-
|
| 626 |
-
if torch.is_grad_enabled() and self.gradient_checkpointing:
|
| 627 |
-
|
| 628 |
-
def create_custom_forward(module, return_dict=None):
|
| 629 |
-
def custom_forward(*inputs):
|
| 630 |
-
if return_dict is not None:
|
| 631 |
-
return module(*inputs, return_dict=return_dict)
|
| 632 |
-
else:
|
| 633 |
-
return module(*inputs)
|
| 634 |
-
|
| 635 |
-
return custom_forward
|
| 636 |
-
|
| 637 |
-
hidden_states = torch.utils.checkpoint.checkpoint(
|
| 638 |
-
create_custom_forward(resnet), hidden_states, temb, use_reentrant=False
|
| 639 |
-
)
|
| 640 |
-
hidden_states = torch.utils.checkpoint.checkpoint(
|
| 641 |
-
create_custom_forward(attn, return_dict=False),
|
| 642 |
-
hidden_states,
|
| 643 |
-
encoder_hidden_states,
|
| 644 |
-
use_reentrant=False,
|
| 645 |
-
)[0]
|
| 646 |
-
|
| 647 |
-
if motion_module is not None:
|
| 648 |
-
hidden_states = torch.utils.checkpoint.checkpoint(
|
| 649 |
-
create_custom_forward(motion_module),
|
| 650 |
-
hidden_states,
|
| 651 |
-
temb,
|
| 652 |
-
encoder_hidden_states,
|
| 653 |
-
use_reentrant=False,
|
| 654 |
-
)
|
| 655 |
-
else:
|
| 656 |
-
hidden_states = resnet(hidden_states, temb)
|
| 657 |
-
hidden_states = attn(hidden_states, encoder_hidden_states=encoder_hidden_states).sample
|
| 658 |
-
|
| 659 |
-
if motion_module is not None:
|
| 660 |
-
hidden_states = motion_module(hidden_states, temb, encoder_hidden_states=encoder_hidden_states)
|
| 661 |
-
|
| 662 |
-
if self.upsamplers is not None:
|
| 663 |
-
for upsampler in self.upsamplers:
|
| 664 |
-
hidden_states = upsampler(hidden_states, upsample_size)
|
| 665 |
-
|
| 666 |
-
return hidden_states
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
class UpBlock3D(nn.Module):
|
| 670 |
-
def __init__(
|
| 671 |
-
self,
|
| 672 |
-
in_channels: int,
|
| 673 |
-
prev_output_channel: int,
|
| 674 |
-
out_channels: int,
|
| 675 |
-
temb_channels: int,
|
| 676 |
-
dropout: float = 0.0,
|
| 677 |
-
num_layers: int = 1,
|
| 678 |
-
resnet_eps: float = 1e-6,
|
| 679 |
-
resnet_time_scale_shift: str = "default",
|
| 680 |
-
resnet_act_fn: str = "swish",
|
| 681 |
-
resnet_groups: int = 32,
|
| 682 |
-
resnet_pre_norm: bool = True,
|
| 683 |
-
output_scale_factor=1.0,
|
| 684 |
-
add_upsample=True,
|
| 685 |
-
use_inflated_groupnorm=False,
|
| 686 |
-
use_motion_module=None,
|
| 687 |
-
motion_module_type=None,
|
| 688 |
-
motion_module_kwargs=None,
|
| 689 |
-
):
|
| 690 |
-
super().__init__()
|
| 691 |
-
resnets = []
|
| 692 |
-
motion_modules = []
|
| 693 |
-
|
| 694 |
-
for i in range(num_layers):
|
| 695 |
-
res_skip_channels = in_channels if (i == num_layers - 1) else out_channels
|
| 696 |
-
resnet_in_channels = prev_output_channel if i == 0 else out_channels
|
| 697 |
-
|
| 698 |
-
resnets.append(
|
| 699 |
-
ResnetBlock3D(
|
| 700 |
-
in_channels=resnet_in_channels + res_skip_channels,
|
| 701 |
-
out_channels=out_channels,
|
| 702 |
-
temb_channels=temb_channels,
|
| 703 |
-
eps=resnet_eps,
|
| 704 |
-
groups=resnet_groups,
|
| 705 |
-
dropout=dropout,
|
| 706 |
-
time_embedding_norm=resnet_time_scale_shift,
|
| 707 |
-
non_linearity=resnet_act_fn,
|
| 708 |
-
output_scale_factor=output_scale_factor,
|
| 709 |
-
pre_norm=resnet_pre_norm,
|
| 710 |
-
use_inflated_groupnorm=use_inflated_groupnorm,
|
| 711 |
-
)
|
| 712 |
-
)
|
| 713 |
-
motion_modules.append(
|
| 714 |
-
get_motion_module(
|
| 715 |
-
in_channels=out_channels,
|
| 716 |
-
motion_module_type=motion_module_type,
|
| 717 |
-
motion_module_kwargs=motion_module_kwargs,
|
| 718 |
-
)
|
| 719 |
-
if use_motion_module
|
| 720 |
-
else None
|
| 721 |
-
)
|
| 722 |
-
|
| 723 |
-
self.resnets = nn.ModuleList(resnets)
|
| 724 |
-
self.motion_modules = nn.ModuleList(motion_modules)
|
| 725 |
-
|
| 726 |
-
if add_upsample:
|
| 727 |
-
self.upsamplers = nn.ModuleList([Upsample3D(out_channels, use_conv=True, out_channels=out_channels)])
|
| 728 |
-
else:
|
| 729 |
-
self.upsamplers = None
|
| 730 |
-
|
| 731 |
-
self.gradient_checkpointing = False
|
| 732 |
-
|
| 733 |
-
def forward(
|
| 734 |
-
self,
|
| 735 |
-
hidden_states,
|
| 736 |
-
res_hidden_states_tuple,
|
| 737 |
-
temb=None,
|
| 738 |
-
upsample_size=None,
|
| 739 |
-
encoder_hidden_states=None,
|
| 740 |
-
):
|
| 741 |
-
for resnet, motion_module in zip(self.resnets, self.motion_modules):
|
| 742 |
-
# pop res hidden states
|
| 743 |
-
res_hidden_states = res_hidden_states_tuple[-1]
|
| 744 |
-
res_hidden_states_tuple = res_hidden_states_tuple[:-1]
|
| 745 |
-
hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1)
|
| 746 |
-
|
| 747 |
-
if torch.is_grad_enabled() and self.gradient_checkpointing:
|
| 748 |
-
|
| 749 |
-
def create_custom_forward(module):
|
| 750 |
-
def custom_forward(*inputs):
|
| 751 |
-
return module(*inputs)
|
| 752 |
-
|
| 753 |
-
return custom_forward
|
| 754 |
-
|
| 755 |
-
hidden_states = torch.utils.checkpoint.checkpoint(
|
| 756 |
-
create_custom_forward(resnet), hidden_states, temb, use_reentrant=False
|
| 757 |
-
)
|
| 758 |
-
|
| 759 |
-
if motion_module is not None:
|
| 760 |
-
hidden_states = torch.utils.checkpoint.checkpoint(
|
| 761 |
-
create_custom_forward(motion_module),
|
| 762 |
-
hidden_states,
|
| 763 |
-
temb,
|
| 764 |
-
encoder_hidden_states,
|
| 765 |
-
use_reentrant=False,
|
| 766 |
-
)
|
| 767 |
-
else:
|
| 768 |
-
hidden_states = resnet(hidden_states, temb)
|
| 769 |
-
|
| 770 |
-
if motion_module is not None:
|
| 771 |
-
hidden_states = motion_module(hidden_states, temb, encoder_hidden_states=encoder_hidden_states)
|
| 772 |
-
|
| 773 |
-
if self.upsamplers is not None:
|
| 774 |
-
for upsampler in self.upsamplers:
|
| 775 |
-
hidden_states = upsampler(hidden_states, upsample_size)
|
| 776 |
-
|
| 777 |
-
return hidden_states
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
latentsync/models/utils.py
DELETED
|
@@ -1,19 +0,0 @@
|
|
| 1 |
-
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
def zero_module(module):
|
| 16 |
-
# Zero out the parameters of a module and return it.
|
| 17 |
-
for p in module.parameters():
|
| 18 |
-
p.detach().zero_()
|
| 19 |
-
return module
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|