This view is limited to 50 files because it contains too many changes. See the raw diff here.
Files changed (50) hide show
  1. .agents/skills/uv-package-manager/SKILL.md +0 -834
  2. .claude/skills/uv-package-manager +0 -1
  3. .dockerignore +0 -38
  4. .github/workflows/docker.yml +0 -57
  5. .gitignore +0 -21
  6. .opencode/skills/uv-package-manager +0 -1
  7. .python-version +0 -1
  8. AGENTS.md +0 -226
  9. Dockerfile +0 -48
  10. ORIGINAL_README.md +0 -106
  11. README.md +5 -156
  12. app.py +0 -189
  13. assets/readme.md +0 -0
  14. audio_processing.py +0 -211
  15. checkpoints/readme.md +0 -0
  16. config.py +0 -25
  17. configs/inference/musetalk.yaml +0 -42
  18. configs/scheduler_config.json +0 -12
  19. configs/unet/stage2_512.yaml +0 -99
  20. docker-compose.yml +0 -19
  21. download_checkpoints.sh +0 -18
  22. download_musetalk_models.py +0 -203
  23. eval/detectors/README.md +0 -3
  24. eval/detectors/__init__.py +0 -1
  25. eval/detectors/s3fd/__init__.py +0 -63
  26. eval/detectors/s3fd/box_utils.py +0 -221
  27. eval/detectors/s3fd/nets.py +0 -174
  28. eval/draw_syncnet_lines.py +0 -64
  29. eval/eval_fvd.py +0 -98
  30. eval/eval_sync_conf.py +0 -77
  31. eval/eval_sync_conf.sh +0 -2
  32. eval/eval_syncnet_acc.py +0 -137
  33. eval/eval_syncnet_acc.sh +0 -3
  34. eval/fvd.py +0 -58
  35. eval/hyper_iqa.py +0 -343
  36. eval/inference_videos.py +0 -77
  37. eval/syncnet/__init__.py +0 -1
  38. eval/syncnet/syncnet.py +0 -113
  39. eval/syncnet/syncnet_eval.py +0 -220
  40. eval/syncnet_detect.py +0 -251
  41. face_processing.py +0 -585
  42. latentsync/data/syncnet_dataset.py +0 -139
  43. latentsync/data/unet_dataset.py +0 -152
  44. latentsync/models/attention.py +0 -280
  45. latentsync/models/motion_module.py +0 -313
  46. latentsync/models/resnet.py +0 -228
  47. latentsync/models/stable_syncnet.py +0 -233
  48. latentsync/models/unet.py +0 -512
  49. latentsync/models/unet_blocks.py +0 -777
  50. 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: LipSync - LatentSync 1.6
3
- emoji: πŸ‘„
4
- colorFrom: purple
5
  colorTo: pink
6
- sdk: gradio
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
- # OutofLipSync - LatentSync 1.6
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