Spaces:
Running
on
Zero
Running
on
Zero
Delete stf/stf-api-tools
Browse files- stf/stf-api-tools/.gitignore +0 -160
- stf/stf-api-tools/.ipynb_checkpoints/README-checkpoint.md +0 -0
- stf/stf-api-tools/.ipynb_checkpoints/pyproject-checkpoint.toml +0 -14
- stf/stf-api-tools/README.md +0 -0
- stf/stf-api-tools/poetry.lock +0 -7
- stf/stf-api-tools/pyproject.toml +0 -14
- stf/stf-api-tools/src/stf_tools/.ipynb_checkpoints/__init__-checkpoint.py +0 -0
- stf/stf-api-tools/src/stf_tools/.ipynb_checkpoints/build_template-checkpoint.py +0 -200
- stf/stf-api-tools/src/stf_tools/.ipynb_checkpoints/silent-checkpoint.py +0 -28
- stf/stf-api-tools/src/stf_tools/__init__.py +0 -0
- stf/stf-api-tools/src/stf_tools/build_template.py +0 -200
- stf/stf-api-tools/src/stf_tools/silent.py +0 -28
- stf/stf-api-tools/src/stf_tools/writers/.ipynb_checkpoints/ffmpeg-checkpoint.py +0 -100
- stf/stf-api-tools/src/stf_tools/writers/.ipynb_checkpoints/webm-checkpoint.py +0 -60
- stf/stf-api-tools/src/stf_tools/writers/__init__.py +0 -2
- stf/stf-api-tools/src/stf_tools/writers/_async.py +0 -42
- stf/stf-api-tools/src/stf_tools/writers/_thread.py +0 -57
- stf/stf-api-tools/src/stf_tools/writers/ffmpeg.py +0 -100
- stf/stf-api-tools/src/stf_tools/writers/webm.py +0 -60
stf/stf-api-tools/.gitignore
DELETED
@@ -1,160 +0,0 @@
|
|
1 |
-
# Byte-compiled / optimized / DLL files
|
2 |
-
__pycache__/
|
3 |
-
*.py[cod]
|
4 |
-
*$py.class
|
5 |
-
|
6 |
-
# C extensions
|
7 |
-
*.so
|
8 |
-
|
9 |
-
# Distribution / packaging
|
10 |
-
.Python
|
11 |
-
build/
|
12 |
-
develop-eggs/
|
13 |
-
dist/
|
14 |
-
downloads/
|
15 |
-
eggs/
|
16 |
-
.eggs/
|
17 |
-
lib/
|
18 |
-
lib64/
|
19 |
-
parts/
|
20 |
-
sdist/
|
21 |
-
var/
|
22 |
-
wheels/
|
23 |
-
share/python-wheels/
|
24 |
-
*.egg-info/
|
25 |
-
.installed.cfg
|
26 |
-
*.egg
|
27 |
-
MANIFEST
|
28 |
-
|
29 |
-
# PyInstaller
|
30 |
-
# Usually these files are written by a python script from a template
|
31 |
-
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
32 |
-
*.manifest
|
33 |
-
*.spec
|
34 |
-
|
35 |
-
# Installer logs
|
36 |
-
pip-log.txt
|
37 |
-
pip-delete-this-directory.txt
|
38 |
-
|
39 |
-
# Unit test / coverage reports
|
40 |
-
htmlcov/
|
41 |
-
.tox/
|
42 |
-
.nox/
|
43 |
-
.coverage
|
44 |
-
.coverage.*
|
45 |
-
.cache
|
46 |
-
nosetests.xml
|
47 |
-
coverage.xml
|
48 |
-
*.cover
|
49 |
-
*.py,cover
|
50 |
-
.hypothesis/
|
51 |
-
.pytest_cache/
|
52 |
-
cover/
|
53 |
-
|
54 |
-
# Translations
|
55 |
-
*.mo
|
56 |
-
*.pot
|
57 |
-
|
58 |
-
# Django stuff:
|
59 |
-
*.log
|
60 |
-
local_settings.py
|
61 |
-
db.sqlite3
|
62 |
-
db.sqlite3-journal
|
63 |
-
|
64 |
-
# Flask stuff:
|
65 |
-
instance/
|
66 |
-
.webassets-cache
|
67 |
-
|
68 |
-
# Scrapy stuff:
|
69 |
-
.scrapy
|
70 |
-
|
71 |
-
# Sphinx documentation
|
72 |
-
docs/_build/
|
73 |
-
|
74 |
-
# PyBuilder
|
75 |
-
.pybuilder/
|
76 |
-
target/
|
77 |
-
|
78 |
-
# Jupyter Notebook
|
79 |
-
.ipynb_checkpoints
|
80 |
-
|
81 |
-
# IPython
|
82 |
-
profile_default/
|
83 |
-
ipython_config.py
|
84 |
-
|
85 |
-
# pyenv
|
86 |
-
# For a library or package, you might want to ignore these files since the code is
|
87 |
-
# intended to run in multiple environments; otherwise, check them in:
|
88 |
-
# .python-version
|
89 |
-
|
90 |
-
# pipenv
|
91 |
-
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
92 |
-
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
93 |
-
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
94 |
-
# install all needed dependencies.
|
95 |
-
#Pipfile.lock
|
96 |
-
|
97 |
-
# poetry
|
98 |
-
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
99 |
-
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
100 |
-
# commonly ignored for libraries.
|
101 |
-
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
102 |
-
#poetry.lock
|
103 |
-
|
104 |
-
# pdm
|
105 |
-
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
106 |
-
#pdm.lock
|
107 |
-
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
108 |
-
# in version control.
|
109 |
-
# https://pdm.fming.dev/#use-with-ide
|
110 |
-
.pdm.toml
|
111 |
-
|
112 |
-
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
113 |
-
__pypackages__/
|
114 |
-
|
115 |
-
# Celery stuff
|
116 |
-
celerybeat-schedule
|
117 |
-
celerybeat.pid
|
118 |
-
|
119 |
-
# SageMath parsed files
|
120 |
-
*.sage.py
|
121 |
-
|
122 |
-
# Environments
|
123 |
-
.env
|
124 |
-
.venv
|
125 |
-
env/
|
126 |
-
venv/
|
127 |
-
ENV/
|
128 |
-
env.bak/
|
129 |
-
venv.bak/
|
130 |
-
|
131 |
-
# Spyder project settings
|
132 |
-
.spyderproject
|
133 |
-
.spyproject
|
134 |
-
|
135 |
-
# Rope project settings
|
136 |
-
.ropeproject
|
137 |
-
|
138 |
-
# mkdocs documentation
|
139 |
-
/site
|
140 |
-
|
141 |
-
# mypy
|
142 |
-
.mypy_cache/
|
143 |
-
.dmypy.json
|
144 |
-
dmypy.json
|
145 |
-
|
146 |
-
# Pyre type checker
|
147 |
-
.pyre/
|
148 |
-
|
149 |
-
# pytype static type analyzer
|
150 |
-
.pytype/
|
151 |
-
|
152 |
-
# Cython debug symbols
|
153 |
-
cython_debug/
|
154 |
-
|
155 |
-
# PyCharm
|
156 |
-
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
157 |
-
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
158 |
-
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
159 |
-
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
160 |
-
#.idea/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stf/stf-api-tools/.ipynb_checkpoints/README-checkpoint.md
DELETED
File without changes
|
stf/stf-api-tools/.ipynb_checkpoints/pyproject-checkpoint.toml
DELETED
@@ -1,14 +0,0 @@
|
|
1 |
-
[tool.poetry]
|
2 |
-
name = "stf-tools"
|
3 |
-
version = "0.1.0"
|
4 |
-
description = "stf-alternative tools"
|
5 |
-
authors = ["Kim Minjong <make.dirty.code@gmail.com>"]
|
6 |
-
readme = "README.md"
|
7 |
-
packages = [
|
8 |
-
{include = "stf_tools", from="src"}
|
9 |
-
]
|
10 |
-
|
11 |
-
|
12 |
-
[build-system]
|
13 |
-
requires = ["poetry-core"]
|
14 |
-
build-backend = "poetry.core.masonry.api"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stf/stf-api-tools/README.md
DELETED
File without changes
|
stf/stf-api-tools/poetry.lock
DELETED
@@ -1,7 +0,0 @@
|
|
1 |
-
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
2 |
-
package = []
|
3 |
-
|
4 |
-
[metadata]
|
5 |
-
lock-version = "2.0"
|
6 |
-
python-versions = "*"
|
7 |
-
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stf/stf-api-tools/pyproject.toml
DELETED
@@ -1,14 +0,0 @@
|
|
1 |
-
[tool.poetry]
|
2 |
-
name = "stf-tools"
|
3 |
-
version = "0.1.0"
|
4 |
-
description = "stf-alternative tools"
|
5 |
-
authors = ["Kim Minjong <make.dirty.code@gmail.com>"]
|
6 |
-
readme = "README.md"
|
7 |
-
packages = [
|
8 |
-
{include = "stf_tools", from="src"}
|
9 |
-
]
|
10 |
-
|
11 |
-
|
12 |
-
[build-system]
|
13 |
-
requires = ["poetry-core"]
|
14 |
-
build-backend = "poetry.core.masonry.api"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stf/stf-api-tools/src/stf_tools/.ipynb_checkpoints/__init__-checkpoint.py
DELETED
File without changes
|
stf/stf-api-tools/src/stf_tools/.ipynb_checkpoints/build_template-checkpoint.py
DELETED
@@ -1,200 +0,0 @@
|
|
1 |
-
import pathlib
|
2 |
-
import subprocess
|
3 |
-
import tempfile
|
4 |
-
|
5 |
-
import av
|
6 |
-
import numpy as np
|
7 |
-
from PIL import Image
|
8 |
-
|
9 |
-
|
10 |
-
def alpha_crop_detect(path):
|
11 |
-
result = subprocess.check_output(
|
12 |
-
[
|
13 |
-
"bash",
|
14 |
-
"-c",
|
15 |
-
f"""ffmpeg -c:v libvpx -i {path} -filter_complex "[0:v]alphaextract, cropdetect=limit=0:round=16:reset=0" -f null - 2>&1 | grep -oP 'crop=\K\d+:\d+:\d+:\d+' """,
|
16 |
-
]
|
17 |
-
)
|
18 |
-
return result.decode().strip().split("\n")[-1]
|
19 |
-
|
20 |
-
|
21 |
-
def crop_resize_overlay(
|
22 |
-
path, background_path, range, out, left=0.5, top=0.15, height=0.85, crf=17
|
23 |
-
):
|
24 |
-
with av.open(path, "r") as f:
|
25 |
-
fps = f.streams.video[0].base_rate
|
26 |
-
|
27 |
-
with av.open(background_path, "r") as f:
|
28 |
-
background_width, background_height = (
|
29 |
-
f.streams.video[0].width,
|
30 |
-
f.streams.video[0].height,
|
31 |
-
)
|
32 |
-
|
33 |
-
if isinstance(top, float):
|
34 |
-
top = int(background_height * top)
|
35 |
-
|
36 |
-
if isinstance(height, float):
|
37 |
-
height = int(background_height * height)
|
38 |
-
|
39 |
-
height -= height % 2
|
40 |
-
|
41 |
-
w, h, _, _ = map(int, range.split(":"))
|
42 |
-
width = int(height / h * w)
|
43 |
-
width -= width % 2
|
44 |
-
|
45 |
-
if isinstance(left, float):
|
46 |
-
left = int(background_width * left) - width // 2
|
47 |
-
|
48 |
-
subprocess.call(
|
49 |
-
[
|
50 |
-
"bash",
|
51 |
-
"-c",
|
52 |
-
f"""ffmpeg -y -c:v libvpx -r {fps} -i {path} -r {fps} -i {background_path} -filter_complex "[0:v]crop={range},scale={width}:{height} [vidi]; [1:v][vidi] overlay={left}:{top}" -crf {crf} -pix_fmt yuva420p -c:v libvpx-vp9 -c:a copy {out}""",
|
53 |
-
]
|
54 |
-
)
|
55 |
-
|
56 |
-
return background_width, background_height, int(fps), (left, top, height)
|
57 |
-
|
58 |
-
|
59 |
-
import json
|
60 |
-
import os
|
61 |
-
import shutil
|
62 |
-
import tempfile
|
63 |
-
from pathlib import Path
|
64 |
-
|
65 |
-
import av
|
66 |
-
import pandas as pd
|
67 |
-
import stf_alternative
|
68 |
-
from stf_alternative.util import get_crop_mp4_dir, get_frame_dir, get_preprocess_dir
|
69 |
-
|
70 |
-
from stf_tools.silent import create_silent_video
|
71 |
-
from stf_tools.writers import WebmWriter
|
72 |
-
|
73 |
-
|
74 |
-
def create_template(
|
75 |
-
template_video_path,
|
76 |
-
background_path,
|
77 |
-
out_path,
|
78 |
-
config_path,
|
79 |
-
reference_face,
|
80 |
-
work_root_path,
|
81 |
-
checkpoint_path,
|
82 |
-
left,
|
83 |
-
top,
|
84 |
-
height,
|
85 |
-
crf=17,
|
86 |
-
):
|
87 |
-
crop_range = alpha_crop_detect(template_video_path)
|
88 |
-
result_width, result_height, fps, (left, top, height) = crop_resize_overlay(
|
89 |
-
template_video_path,
|
90 |
-
background_path,
|
91 |
-
crop_range,
|
92 |
-
out_path,
|
93 |
-
left=left,
|
94 |
-
top=top,
|
95 |
-
height=height,
|
96 |
-
crf=crf,
|
97 |
-
)
|
98 |
-
|
99 |
-
stf_alternative.preprocess_template(
|
100 |
-
config_path=config_path,
|
101 |
-
template_video_path=template_video_path,
|
102 |
-
reference_face=reference_face,
|
103 |
-
work_root_path=work_root_path,
|
104 |
-
template_frame_ratio=1.0,
|
105 |
-
template_video_ratio=[1.0],
|
106 |
-
silent_video_path=None,
|
107 |
-
callback=None,
|
108 |
-
device="cuda:0",
|
109 |
-
verbose=True,
|
110 |
-
save_frames=False,
|
111 |
-
)
|
112 |
-
|
113 |
-
model = stf_alternative.create_model(
|
114 |
-
config_path=config_path,
|
115 |
-
checkpoint_path=checkpoint_path,
|
116 |
-
work_root_path=work_root_path,
|
117 |
-
device="cuda:0",
|
118 |
-
verbose=True,
|
119 |
-
wavlm_path="microsoft/wavlm-large",
|
120 |
-
)
|
121 |
-
|
122 |
-
preprocess_dir = Path(get_preprocess_dir(work_root_path, model.args.name))
|
123 |
-
crop_mp4_dir = Path(get_crop_mp4_dir(preprocess_dir, template_video_path))
|
124 |
-
dataset_dir = crop_mp4_dir / f"{Path(template_video_path).stem}_000"
|
125 |
-
template_frames_path = Path(
|
126 |
-
get_frame_dir(preprocess_dir, template_video_path, ratio=1.0)
|
127 |
-
)
|
128 |
-
|
129 |
-
with open(preprocess_dir / "metadata.json", "w") as f:
|
130 |
-
json.dump(
|
131 |
-
{
|
132 |
-
"fps": fps,
|
133 |
-
"width": result_width,
|
134 |
-
"height": result_height,
|
135 |
-
},
|
136 |
-
f,
|
137 |
-
)
|
138 |
-
|
139 |
-
df = pd.read_pickle(dataset_dir / "df_fan.pickle")
|
140 |
-
|
141 |
-
w, h, x, y = map(int, crop_range.split(":"))
|
142 |
-
scale = height / h
|
143 |
-
|
144 |
-
id_set = set()
|
145 |
-
for it in df["cropped_box"]:
|
146 |
-
if id(it) in id_set:
|
147 |
-
continue
|
148 |
-
id_set.add(id(it))
|
149 |
-
x1, y1, x2, y2 = it
|
150 |
-
x1 = (x1 - x) * scale + left
|
151 |
-
x2 = (x2 - x) * scale + left
|
152 |
-
y1 = (y1 - y) * scale + top
|
153 |
-
y2 = (y2 - y) * scale + top
|
154 |
-
it[:] = (x1, y1, x2, y2)
|
155 |
-
|
156 |
-
df.to_pickle(dataset_dir / "df_fan.pickle")
|
157 |
-
|
158 |
-
template_frames_path.mkdir(exist_ok=True, parents=True)
|
159 |
-
with av.open(out_path) as container:
|
160 |
-
for frame in container.decode(video=0):
|
161 |
-
Image.fromarray(frame.to_ndarray(format="rgb24"), mode="RGB").save(
|
162 |
-
f"{template_frames_path}/%05d.webp" % frame.index,
|
163 |
-
format="webp",
|
164 |
-
lossless=True,
|
165 |
-
)
|
166 |
-
|
167 |
-
with tempfile.TemporaryDirectory() as tempdir:
|
168 |
-
silent_video_path = f"{tempdir}/silent.webm"
|
169 |
-
template = stf_alternative.Template(
|
170 |
-
config_path=config_path,
|
171 |
-
model=model,
|
172 |
-
template_video_path=template_video_path,
|
173 |
-
wav_std=False,
|
174 |
-
ref_wav=None,
|
175 |
-
verbose=True,
|
176 |
-
)
|
177 |
-
writer = WebmWriter(
|
178 |
-
silent_video_path,
|
179 |
-
width=result_width,
|
180 |
-
height=result_height,
|
181 |
-
fps=fps,
|
182 |
-
crf=crf,
|
183 |
-
audio_sample_rate=16000,
|
184 |
-
quiet=False,
|
185 |
-
)
|
186 |
-
create_silent_video(template, writer)
|
187 |
-
|
188 |
-
silent_frames_path = Path(
|
189 |
-
get_frame_dir(preprocess_dir, silent_video_path, ratio=1.0)
|
190 |
-
)
|
191 |
-
silent_frames_path.mkdir(exist_ok=True, parents=True)
|
192 |
-
with av.open(silent_video_path) as container:
|
193 |
-
for frame in container.decode(video=0):
|
194 |
-
Image.fromarray(frame.to_ndarray(format="rgb24"), mode="RGB").save(
|
195 |
-
f"{silent_frames_path}/%05d.webp" % frame.index,
|
196 |
-
format="webp",
|
197 |
-
lossless=True,
|
198 |
-
)
|
199 |
-
shutil.rmtree(template_frames_path, ignore_errors=False)
|
200 |
-
silent_frames_path.rename(template_frames_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stf/stf-api-tools/src/stf_tools/.ipynb_checkpoints/silent-checkpoint.py
DELETED
@@ -1,28 +0,0 @@
|
|
1 |
-
from concurrent.futures import ThreadPoolExecutor
|
2 |
-
|
3 |
-
from pydub import AudioSegment
|
4 |
-
|
5 |
-
|
6 |
-
def create_silent_video(template, writer):
|
7 |
-
reader = iter(template._get_reader(num_skip_frames=0))
|
8 |
-
audio_segment = AudioSegment.silent(10000)
|
9 |
-
pivot = 0
|
10 |
-
|
11 |
-
with ThreadPoolExecutor(4) as p:
|
12 |
-
try:
|
13 |
-
while True:
|
14 |
-
gen_infer = template.gen_infer_concurrent(
|
15 |
-
p,
|
16 |
-
audio_segment,
|
17 |
-
pivot,
|
18 |
-
)
|
19 |
-
for idx, (it, chunk) in enumerate(gen_infer, pivot):
|
20 |
-
frame = next(reader)
|
21 |
-
composed = template.compose(idx, frame, it)
|
22 |
-
writer.video_writer.write(composed)
|
23 |
-
writer.audio_writer.write(chunk)
|
24 |
-
pivot = idx + 1
|
25 |
-
except StopIteration as e:
|
26 |
-
pass
|
27 |
-
|
28 |
-
writer.finish(forced=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stf/stf-api-tools/src/stf_tools/__init__.py
DELETED
File without changes
|
stf/stf-api-tools/src/stf_tools/build_template.py
DELETED
@@ -1,200 +0,0 @@
|
|
1 |
-
import pathlib
|
2 |
-
import subprocess
|
3 |
-
import tempfile
|
4 |
-
|
5 |
-
import av
|
6 |
-
import numpy as np
|
7 |
-
from PIL import Image
|
8 |
-
|
9 |
-
|
10 |
-
def alpha_crop_detect(path):
|
11 |
-
result = subprocess.check_output(
|
12 |
-
[
|
13 |
-
"bash",
|
14 |
-
"-c",
|
15 |
-
f"""ffmpeg -c:v libvpx -i {path} -filter_complex "[0:v]alphaextract, cropdetect=limit=0:round=16:reset=0" -f null - 2>&1 | grep -oP 'crop=\K\d+:\d+:\d+:\d+' """,
|
16 |
-
]
|
17 |
-
)
|
18 |
-
return result.decode().strip().split("\n")[-1]
|
19 |
-
|
20 |
-
|
21 |
-
def crop_resize_overlay(
|
22 |
-
path, background_path, range, out, left=0.5, top=0.15, height=0.85, crf=17
|
23 |
-
):
|
24 |
-
with av.open(path, "r") as f:
|
25 |
-
fps = f.streams.video[0].base_rate
|
26 |
-
|
27 |
-
with av.open(background_path, "r") as f:
|
28 |
-
background_width, background_height = (
|
29 |
-
f.streams.video[0].width,
|
30 |
-
f.streams.video[0].height,
|
31 |
-
)
|
32 |
-
|
33 |
-
if isinstance(top, float):
|
34 |
-
top = int(background_height * top)
|
35 |
-
|
36 |
-
if isinstance(height, float):
|
37 |
-
height = int(background_height * height)
|
38 |
-
|
39 |
-
height -= height % 2
|
40 |
-
|
41 |
-
w, h, _, _ = map(int, range.split(":"))
|
42 |
-
width = int(height / h * w)
|
43 |
-
width -= width % 2
|
44 |
-
|
45 |
-
if isinstance(left, float):
|
46 |
-
left = int(background_width * left) - width // 2
|
47 |
-
|
48 |
-
subprocess.call(
|
49 |
-
[
|
50 |
-
"bash",
|
51 |
-
"-c",
|
52 |
-
f"""ffmpeg -y -c:v libvpx -r {fps} -i {path} -r {fps} -i {background_path} -filter_complex "[0:v]crop={range},scale={width}:{height} [vidi]; [1:v][vidi] overlay={left}:{top}" -crf {crf} -pix_fmt yuva420p -c:v libvpx-vp9 -c:a copy {out}""",
|
53 |
-
]
|
54 |
-
)
|
55 |
-
|
56 |
-
return background_width, background_height, int(fps), (left, top, height)
|
57 |
-
|
58 |
-
|
59 |
-
import json
|
60 |
-
import os
|
61 |
-
import shutil
|
62 |
-
import tempfile
|
63 |
-
from pathlib import Path
|
64 |
-
|
65 |
-
import av
|
66 |
-
import pandas as pd
|
67 |
-
import stf_alternative
|
68 |
-
from stf_alternative.util import get_crop_mp4_dir, get_frame_dir, get_preprocess_dir
|
69 |
-
|
70 |
-
from stf_tools.silent import create_silent_video
|
71 |
-
from stf_tools.writers import WebmWriter
|
72 |
-
|
73 |
-
|
74 |
-
def create_template(
|
75 |
-
template_video_path,
|
76 |
-
background_path,
|
77 |
-
out_path,
|
78 |
-
config_path,
|
79 |
-
reference_face,
|
80 |
-
work_root_path,
|
81 |
-
checkpoint_path,
|
82 |
-
left,
|
83 |
-
top,
|
84 |
-
height,
|
85 |
-
crf=17,
|
86 |
-
):
|
87 |
-
crop_range = alpha_crop_detect(template_video_path)
|
88 |
-
result_width, result_height, fps, (left, top, height) = crop_resize_overlay(
|
89 |
-
template_video_path,
|
90 |
-
background_path,
|
91 |
-
crop_range,
|
92 |
-
out_path,
|
93 |
-
left=left,
|
94 |
-
top=top,
|
95 |
-
height=height,
|
96 |
-
crf=crf,
|
97 |
-
)
|
98 |
-
|
99 |
-
stf_alternative.preprocess_template(
|
100 |
-
config_path=config_path,
|
101 |
-
template_video_path=template_video_path,
|
102 |
-
reference_face=reference_face,
|
103 |
-
work_root_path=work_root_path,
|
104 |
-
template_frame_ratio=1.0,
|
105 |
-
template_video_ratio=[1.0],
|
106 |
-
silent_video_path=None,
|
107 |
-
callback=None,
|
108 |
-
device="cuda:0",
|
109 |
-
verbose=True,
|
110 |
-
save_frames=False,
|
111 |
-
)
|
112 |
-
|
113 |
-
model = stf_alternative.create_model(
|
114 |
-
config_path=config_path,
|
115 |
-
checkpoint_path=checkpoint_path,
|
116 |
-
work_root_path=work_root_path,
|
117 |
-
device="cuda:0",
|
118 |
-
verbose=True,
|
119 |
-
wavlm_path="microsoft/wavlm-large",
|
120 |
-
)
|
121 |
-
|
122 |
-
preprocess_dir = Path(get_preprocess_dir(work_root_path, model.args.name))
|
123 |
-
crop_mp4_dir = Path(get_crop_mp4_dir(preprocess_dir, template_video_path))
|
124 |
-
dataset_dir = crop_mp4_dir / f"{Path(template_video_path).stem}_000"
|
125 |
-
template_frames_path = Path(
|
126 |
-
get_frame_dir(preprocess_dir, template_video_path, ratio=1.0)
|
127 |
-
)
|
128 |
-
|
129 |
-
with open(preprocess_dir / "metadata.json", "w") as f:
|
130 |
-
json.dump(
|
131 |
-
{
|
132 |
-
"fps": fps,
|
133 |
-
"width": result_width,
|
134 |
-
"height": result_height,
|
135 |
-
},
|
136 |
-
f,
|
137 |
-
)
|
138 |
-
|
139 |
-
df = pd.read_pickle(dataset_dir / "df_fan.pickle")
|
140 |
-
|
141 |
-
w, h, x, y = map(int, crop_range.split(":"))
|
142 |
-
scale = height / h
|
143 |
-
|
144 |
-
id_set = set()
|
145 |
-
for it in df["cropped_box"]:
|
146 |
-
if id(it) in id_set:
|
147 |
-
continue
|
148 |
-
id_set.add(id(it))
|
149 |
-
x1, y1, x2, y2 = it
|
150 |
-
x1 = (x1 - x) * scale + left
|
151 |
-
x2 = (x2 - x) * scale + left
|
152 |
-
y1 = (y1 - y) * scale + top
|
153 |
-
y2 = (y2 - y) * scale + top
|
154 |
-
it[:] = (x1, y1, x2, y2)
|
155 |
-
|
156 |
-
df.to_pickle(dataset_dir / "df_fan.pickle")
|
157 |
-
|
158 |
-
template_frames_path.mkdir(exist_ok=True, parents=True)
|
159 |
-
with av.open(out_path) as container:
|
160 |
-
for frame in container.decode(video=0):
|
161 |
-
Image.fromarray(frame.to_ndarray(format="rgb24"), mode="RGB").save(
|
162 |
-
f"{template_frames_path}/%05d.webp" % frame.index,
|
163 |
-
format="webp",
|
164 |
-
lossless=True,
|
165 |
-
)
|
166 |
-
|
167 |
-
with tempfile.TemporaryDirectory() as tempdir:
|
168 |
-
silent_video_path = f"{tempdir}/silent.webm"
|
169 |
-
template = stf_alternative.Template(
|
170 |
-
config_path=config_path,
|
171 |
-
model=model,
|
172 |
-
template_video_path=template_video_path,
|
173 |
-
wav_std=False,
|
174 |
-
ref_wav=None,
|
175 |
-
verbose=True,
|
176 |
-
)
|
177 |
-
writer = WebmWriter(
|
178 |
-
silent_video_path,
|
179 |
-
width=result_width,
|
180 |
-
height=result_height,
|
181 |
-
fps=fps,
|
182 |
-
crf=crf,
|
183 |
-
audio_sample_rate=16000,
|
184 |
-
quiet=False,
|
185 |
-
)
|
186 |
-
create_silent_video(template, writer)
|
187 |
-
|
188 |
-
silent_frames_path = Path(
|
189 |
-
get_frame_dir(preprocess_dir, silent_video_path, ratio=1.0)
|
190 |
-
)
|
191 |
-
silent_frames_path.mkdir(exist_ok=True, parents=True)
|
192 |
-
with av.open(silent_video_path) as container:
|
193 |
-
for frame in container.decode(video=0):
|
194 |
-
Image.fromarray(frame.to_ndarray(format="rgb24"), mode="RGB").save(
|
195 |
-
f"{silent_frames_path}/%05d.webp" % frame.index,
|
196 |
-
format="webp",
|
197 |
-
lossless=True,
|
198 |
-
)
|
199 |
-
shutil.rmtree(template_frames_path, ignore_errors=False)
|
200 |
-
silent_frames_path.rename(template_frames_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stf/stf-api-tools/src/stf_tools/silent.py
DELETED
@@ -1,28 +0,0 @@
|
|
1 |
-
from concurrent.futures import ThreadPoolExecutor
|
2 |
-
|
3 |
-
from pydub import AudioSegment
|
4 |
-
|
5 |
-
|
6 |
-
def create_silent_video(template, writer):
|
7 |
-
reader = iter(template._get_reader(num_skip_frames=0))
|
8 |
-
audio_segment = AudioSegment.silent(10000)
|
9 |
-
pivot = 0
|
10 |
-
|
11 |
-
with ThreadPoolExecutor(4) as p:
|
12 |
-
try:
|
13 |
-
while True:
|
14 |
-
gen_infer = template.gen_infer_concurrent(
|
15 |
-
p,
|
16 |
-
audio_segment,
|
17 |
-
pivot,
|
18 |
-
)
|
19 |
-
for idx, (it, chunk) in enumerate(gen_infer, pivot):
|
20 |
-
frame = next(reader)
|
21 |
-
composed = template.compose(idx, frame, it)
|
22 |
-
writer.video_writer.write(composed)
|
23 |
-
writer.audio_writer.write(chunk)
|
24 |
-
pivot = idx + 1
|
25 |
-
except StopIteration as e:
|
26 |
-
pass
|
27 |
-
|
28 |
-
writer.finish(forced=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stf/stf-api-tools/src/stf_tools/writers/.ipynb_checkpoints/ffmpeg-checkpoint.py
DELETED
@@ -1,100 +0,0 @@
|
|
1 |
-
import os
|
2 |
-
import queue
|
3 |
-
import shutil
|
4 |
-
import subprocess
|
5 |
-
import tempfile
|
6 |
-
import threading
|
7 |
-
import time
|
8 |
-
import traceback
|
9 |
-
from abc import ABC, abstractmethod
|
10 |
-
from contextlib import contextmanager
|
11 |
-
from pathlib import Path
|
12 |
-
from queue import Queue
|
13 |
-
|
14 |
-
import ffmpeg
|
15 |
-
import numpy as np
|
16 |
-
import pydub
|
17 |
-
from pydub import AudioSegment
|
18 |
-
|
19 |
-
from stf_tools.writers._async import AudioAsyncWriter, VideoAsyncWriter
|
20 |
-
from stf_tools.writers._thread import AudioThreadWriter, VideoThreadWriter
|
21 |
-
|
22 |
-
video_pipe_name = "video"
|
23 |
-
audio_pipe_name = "audio"
|
24 |
-
|
25 |
-
|
26 |
-
class BaseFFMPEGWriter(ABC):
|
27 |
-
def __init__(
|
28 |
-
self,
|
29 |
-
path,
|
30 |
-
width,
|
31 |
-
height,
|
32 |
-
fps,
|
33 |
-
crf=17,
|
34 |
-
audio_sample_rate=16000,
|
35 |
-
quiet=True,
|
36 |
-
):
|
37 |
-
self.path = Path(path)
|
38 |
-
self.width = width
|
39 |
-
self.height = height
|
40 |
-
self.fps = fps
|
41 |
-
self.crf = crf
|
42 |
-
|
43 |
-
self.path.parent.mkdir(exist_ok=True, parents=True)
|
44 |
-
|
45 |
-
pipe_root = tempfile.mkdtemp()
|
46 |
-
self.pipe_dir = Path(pipe_root)
|
47 |
-
self.video_pipe_path = self.pipe_dir / video_pipe_name
|
48 |
-
self.audio_pipe_path = self.pipe_dir / audio_pipe_name
|
49 |
-
|
50 |
-
os.mkfifo(self.video_pipe_path)
|
51 |
-
os.mkfifo(self.audio_pipe_path)
|
52 |
-
|
53 |
-
self.audio_sample_rate = audio_sample_rate
|
54 |
-
|
55 |
-
self.write_process = self._run_ffmpeg(
|
56 |
-
quiet=quiet,
|
57 |
-
)
|
58 |
-
|
59 |
-
@abstractmethod
|
60 |
-
def _run_ffmpeg(self, quiet):
|
61 |
-
"""ffmpeg writer using named pipe"""
|
62 |
-
|
63 |
-
|
64 |
-
class ThreadFFMPEGWriter(BaseFFMPEGWriter):
|
65 |
-
def __init__(self, *args, **kwargs):
|
66 |
-
super().__init__(*args, **kwargs)
|
67 |
-
|
68 |
-
self.video_writer = VideoThreadWriter(self.video_pipe_path, self.fps)
|
69 |
-
self.audio_writer = AudioThreadWriter(
|
70 |
-
self.audio_pipe_path, self.audio_sample_rate
|
71 |
-
)
|
72 |
-
|
73 |
-
def finish(self, forced=False):
|
74 |
-
self.video_writer.finish(forced=forced)
|
75 |
-
self.audio_writer.finish(forced=forced)
|
76 |
-
|
77 |
-
if forced:
|
78 |
-
self.write_process.kill()
|
79 |
-
else:
|
80 |
-
self.write_process.wait()
|
81 |
-
|
82 |
-
shutil.rmtree(self.pipe_dir, ignore_errors=True)
|
83 |
-
|
84 |
-
|
85 |
-
class AsyncFFMPEGWriter(BaseFFMPEGWriter):
|
86 |
-
def __init__(self, *args, **kwargs):
|
87 |
-
super().__init__(*args, **kwargs)
|
88 |
-
|
89 |
-
self.video_writer = VideoAsyncWriter(self.video_pipe_path, self.fps)
|
90 |
-
self.audio_writer = AudioAsyncWriter(
|
91 |
-
self.audio_pipe_path, self.audio_sample_rate
|
92 |
-
)
|
93 |
-
|
94 |
-
def finish(self, forced=False):
|
95 |
-
if forced:
|
96 |
-
self.write_process.kill()
|
97 |
-
else:
|
98 |
-
self.write_process.wait()
|
99 |
-
|
100 |
-
shutil.rmtree(self.pipe_dir, ignore_errors=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stf/stf-api-tools/src/stf_tools/writers/.ipynb_checkpoints/webm-checkpoint.py
DELETED
@@ -1,60 +0,0 @@
|
|
1 |
-
import subprocess
|
2 |
-
|
3 |
-
from stf_tools.writers.ffmpeg import ThreadFFMPEGWriter
|
4 |
-
|
5 |
-
|
6 |
-
class WebmWriter(ThreadFFMPEGWriter):
|
7 |
-
def _run_ffmpeg(self, quiet):
|
8 |
-
return subprocess.Popen(
|
9 |
-
[
|
10 |
-
"ffmpeg",
|
11 |
-
"-f",
|
12 |
-
"rawvideo",
|
13 |
-
"-pix_fmt",
|
14 |
-
"rgba",
|
15 |
-
"-r",
|
16 |
-
f"{self.fps}",
|
17 |
-
"-s",
|
18 |
-
f"{self.width}x{self.height}",
|
19 |
-
"-thread_queue_size",
|
20 |
-
"1024",
|
21 |
-
"-probesize",
|
22 |
-
f"{self.width*self.height}",
|
23 |
-
"-i",
|
24 |
-
self.video_pipe_path,
|
25 |
-
"-f",
|
26 |
-
"s16le",
|
27 |
-
"-ac",
|
28 |
-
"1",
|
29 |
-
"-acodec",
|
30 |
-
"pcm_s16le",
|
31 |
-
"-ar",
|
32 |
-
"16k",
|
33 |
-
"-thread_queue_size",
|
34 |
-
"4096",
|
35 |
-
"-probesize",
|
36 |
-
"32",
|
37 |
-
"-i",
|
38 |
-
self.audio_pipe_path,
|
39 |
-
"-map",
|
40 |
-
"0:v:0",
|
41 |
-
"-map",
|
42 |
-
"1:a:0",
|
43 |
-
"-pix_fmt",
|
44 |
-
"yuva420p",
|
45 |
-
"-crf",
|
46 |
-
f"{self.crf}",
|
47 |
-
"-r",
|
48 |
-
f"{self.fps}",
|
49 |
-
"-s",
|
50 |
-
f"{self.width//2*2}x{self.height//2*2}",
|
51 |
-
"-threads",
|
52 |
-
"16",
|
53 |
-
"-vcodec",
|
54 |
-
"libvpx-vp9",
|
55 |
-
str(self.path),
|
56 |
-
"-y",
|
57 |
-
],
|
58 |
-
stdout=subprocess.DEVNULL if quiet else None,
|
59 |
-
stderr=subprocess.STDOUT if quiet else None,
|
60 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stf/stf-api-tools/src/stf_tools/writers/__init__.py
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
from stf_tools.writers.ffmpeg import AsyncFFMPEGWriter, ThreadFFMPEGWriter
|
2 |
-
from stf_tools.writers.webm import WebmWriter
|
|
|
|
|
|
stf/stf-api-tools/src/stf_tools/writers/_async.py
DELETED
@@ -1,42 +0,0 @@
|
|
1 |
-
import asyncio
|
2 |
-
|
3 |
-
import aiofiles
|
4 |
-
import numpy as np
|
5 |
-
import pydub
|
6 |
-
|
7 |
-
|
8 |
-
class AsyncWriter:
|
9 |
-
def __init__(self, path):
|
10 |
-
self.queue = asyncio.Queue(maxsize=240)
|
11 |
-
self.path = path
|
12 |
-
|
13 |
-
async def pipeline(self):
|
14 |
-
try:
|
15 |
-
async with aiofiles.open(self.path, "wb", 0) as f:
|
16 |
-
while (bytes := await self.queue.get()) is not None:
|
17 |
-
await f.write(bytes)
|
18 |
-
except:
|
19 |
-
pass
|
20 |
-
|
21 |
-
async def write_bytes(self, bytes):
|
22 |
-
await self.queue.put(bytes)
|
23 |
-
|
24 |
-
|
25 |
-
class VideoAsyncWriter(AsyncWriter):
|
26 |
-
def __init__(self, path, fps):
|
27 |
-
super().__init__(path)
|
28 |
-
self.fps = fps
|
29 |
-
|
30 |
-
async def write(self, video: np.array):
|
31 |
-
return await self.write_bytes(video.astype(np.uint8).tobytes())
|
32 |
-
|
33 |
-
|
34 |
-
class AudioAsyncWriter(AsyncWriter):
|
35 |
-
def __init__(self, path, audio_sample_rate):
|
36 |
-
super().__init__(path)
|
37 |
-
self.audio_sample_rate = audio_sample_rate
|
38 |
-
|
39 |
-
async def write(self, audio: pydub.AudioSegment):
|
40 |
-
return await self.write_bytes(
|
41 |
-
audio.set_frame_rate(self.audio_sample_rate).raw_data
|
42 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stf/stf-api-tools/src/stf_tools/writers/_thread.py
DELETED
@@ -1,57 +0,0 @@
|
|
1 |
-
import threading
|
2 |
-
import time
|
3 |
-
import traceback
|
4 |
-
from queue import Queue
|
5 |
-
|
6 |
-
import numpy as np
|
7 |
-
import pydub
|
8 |
-
|
9 |
-
|
10 |
-
class ThreadWriter:
|
11 |
-
def __init__(self, path):
|
12 |
-
queue = Queue(maxsize=240)
|
13 |
-
self.finished = False
|
14 |
-
|
15 |
-
def write_bytes():
|
16 |
-
try:
|
17 |
-
with open(path, "wb", 0) as f:
|
18 |
-
while (bytes := queue.get()) is not None:
|
19 |
-
f.write(bytes)
|
20 |
-
except Exception as e:
|
21 |
-
traceback.print_exc()
|
22 |
-
self.finished = True
|
23 |
-
|
24 |
-
self.thread = threading.Thread(target=write_bytes)
|
25 |
-
self.queue = queue
|
26 |
-
|
27 |
-
self.thread.start()
|
28 |
-
|
29 |
-
def write_bytes(self, bytes):
|
30 |
-
if self.finished:
|
31 |
-
return
|
32 |
-
self.queue.put(bytes)
|
33 |
-
|
34 |
-
def finish(self, forced=False):
|
35 |
-
self.queue.put(None)
|
36 |
-
if forced:
|
37 |
-
self.finished = True
|
38 |
-
else:
|
39 |
-
self.thread.join()
|
40 |
-
|
41 |
-
|
42 |
-
class VideoThreadWriter(ThreadWriter):
|
43 |
-
def __init__(self, path, fps):
|
44 |
-
super().__init__(path)
|
45 |
-
self.fps = fps
|
46 |
-
|
47 |
-
def write(self, video: np.array):
|
48 |
-
return self.write_bytes(video.astype(np.uint8).tobytes())
|
49 |
-
|
50 |
-
|
51 |
-
class AudioThreadWriter(ThreadWriter):
|
52 |
-
def __init__(self, path, audio_sample_rate):
|
53 |
-
super().__init__(path)
|
54 |
-
self.audio_sample_rate = audio_sample_rate
|
55 |
-
|
56 |
-
def write(self, audio: pydub.AudioSegment):
|
57 |
-
return self.write_bytes(audio.set_frame_rate(self.audio_sample_rate).raw_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stf/stf-api-tools/src/stf_tools/writers/ffmpeg.py
DELETED
@@ -1,100 +0,0 @@
|
|
1 |
-
import os
|
2 |
-
import queue
|
3 |
-
import shutil
|
4 |
-
import subprocess
|
5 |
-
import tempfile
|
6 |
-
import threading
|
7 |
-
import time
|
8 |
-
import traceback
|
9 |
-
from abc import ABC, abstractmethod
|
10 |
-
from contextlib import contextmanager
|
11 |
-
from pathlib import Path
|
12 |
-
from queue import Queue
|
13 |
-
|
14 |
-
import ffmpeg
|
15 |
-
import numpy as np
|
16 |
-
import pydub
|
17 |
-
from pydub import AudioSegment
|
18 |
-
|
19 |
-
from stf_tools.writers._async import AudioAsyncWriter, VideoAsyncWriter
|
20 |
-
from stf_tools.writers._thread import AudioThreadWriter, VideoThreadWriter
|
21 |
-
|
22 |
-
video_pipe_name = "video"
|
23 |
-
audio_pipe_name = "audio"
|
24 |
-
|
25 |
-
|
26 |
-
class BaseFFMPEGWriter(ABC):
|
27 |
-
def __init__(
|
28 |
-
self,
|
29 |
-
path,
|
30 |
-
width,
|
31 |
-
height,
|
32 |
-
fps,
|
33 |
-
crf=17,
|
34 |
-
audio_sample_rate=16000,
|
35 |
-
quiet=True,
|
36 |
-
):
|
37 |
-
self.path = Path(path)
|
38 |
-
self.width = width
|
39 |
-
self.height = height
|
40 |
-
self.fps = fps
|
41 |
-
self.crf = crf
|
42 |
-
|
43 |
-
self.path.parent.mkdir(exist_ok=True, parents=True)
|
44 |
-
|
45 |
-
pipe_root = tempfile.mkdtemp()
|
46 |
-
self.pipe_dir = Path(pipe_root)
|
47 |
-
self.video_pipe_path = self.pipe_dir / video_pipe_name
|
48 |
-
self.audio_pipe_path = self.pipe_dir / audio_pipe_name
|
49 |
-
|
50 |
-
os.mkfifo(self.video_pipe_path)
|
51 |
-
os.mkfifo(self.audio_pipe_path)
|
52 |
-
|
53 |
-
self.audio_sample_rate = audio_sample_rate
|
54 |
-
|
55 |
-
self.write_process = self._run_ffmpeg(
|
56 |
-
quiet=quiet,
|
57 |
-
)
|
58 |
-
|
59 |
-
@abstractmethod
|
60 |
-
def _run_ffmpeg(self, quiet):
|
61 |
-
"""ffmpeg writer using named pipe"""
|
62 |
-
|
63 |
-
|
64 |
-
class ThreadFFMPEGWriter(BaseFFMPEGWriter):
|
65 |
-
def __init__(self, *args, **kwargs):
|
66 |
-
super().__init__(*args, **kwargs)
|
67 |
-
|
68 |
-
self.video_writer = VideoThreadWriter(self.video_pipe_path, self.fps)
|
69 |
-
self.audio_writer = AudioThreadWriter(
|
70 |
-
self.audio_pipe_path, self.audio_sample_rate
|
71 |
-
)
|
72 |
-
|
73 |
-
def finish(self, forced=False):
|
74 |
-
self.video_writer.finish(forced=forced)
|
75 |
-
self.audio_writer.finish(forced=forced)
|
76 |
-
|
77 |
-
if forced:
|
78 |
-
self.write_process.kill()
|
79 |
-
else:
|
80 |
-
self.write_process.wait()
|
81 |
-
|
82 |
-
shutil.rmtree(self.pipe_dir, ignore_errors=True)
|
83 |
-
|
84 |
-
|
85 |
-
class AsyncFFMPEGWriter(BaseFFMPEGWriter):
|
86 |
-
def __init__(self, *args, **kwargs):
|
87 |
-
super().__init__(*args, **kwargs)
|
88 |
-
|
89 |
-
self.video_writer = VideoAsyncWriter(self.video_pipe_path, self.fps)
|
90 |
-
self.audio_writer = AudioAsyncWriter(
|
91 |
-
self.audio_pipe_path, self.audio_sample_rate
|
92 |
-
)
|
93 |
-
|
94 |
-
def finish(self, forced=False):
|
95 |
-
if forced:
|
96 |
-
self.write_process.kill()
|
97 |
-
else:
|
98 |
-
self.write_process.wait()
|
99 |
-
|
100 |
-
shutil.rmtree(self.pipe_dir, ignore_errors=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stf/stf-api-tools/src/stf_tools/writers/webm.py
DELETED
@@ -1,60 +0,0 @@
|
|
1 |
-
import subprocess
|
2 |
-
|
3 |
-
from stf_tools.writers.ffmpeg import ThreadFFMPEGWriter
|
4 |
-
|
5 |
-
|
6 |
-
class WebmWriter(ThreadFFMPEGWriter):
|
7 |
-
def _run_ffmpeg(self, quiet):
|
8 |
-
return subprocess.Popen(
|
9 |
-
[
|
10 |
-
"ffmpeg",
|
11 |
-
"-f",
|
12 |
-
"rawvideo",
|
13 |
-
"-pix_fmt",
|
14 |
-
"rgba",
|
15 |
-
"-r",
|
16 |
-
f"{self.fps}",
|
17 |
-
"-s",
|
18 |
-
f"{self.width}x{self.height}",
|
19 |
-
"-thread_queue_size",
|
20 |
-
"1024",
|
21 |
-
"-probesize",
|
22 |
-
f"{self.width*self.height}",
|
23 |
-
"-i",
|
24 |
-
self.video_pipe_path,
|
25 |
-
"-f",
|
26 |
-
"s16le",
|
27 |
-
"-ac",
|
28 |
-
"1",
|
29 |
-
"-acodec",
|
30 |
-
"pcm_s16le",
|
31 |
-
"-ar",
|
32 |
-
"16k",
|
33 |
-
"-thread_queue_size",
|
34 |
-
"4096",
|
35 |
-
"-probesize",
|
36 |
-
"32",
|
37 |
-
"-i",
|
38 |
-
self.audio_pipe_path,
|
39 |
-
"-map",
|
40 |
-
"0:v:0",
|
41 |
-
"-map",
|
42 |
-
"1:a:0",
|
43 |
-
"-pix_fmt",
|
44 |
-
"yuva420p",
|
45 |
-
"-crf",
|
46 |
-
f"{self.crf}",
|
47 |
-
"-r",
|
48 |
-
f"{self.fps}",
|
49 |
-
"-s",
|
50 |
-
f"{self.width//2*2}x{self.height//2*2}",
|
51 |
-
"-threads",
|
52 |
-
"16",
|
53 |
-
"-vcodec",
|
54 |
-
"libvpx-vp9",
|
55 |
-
str(self.path),
|
56 |
-
"-y",
|
57 |
-
],
|
58 |
-
stdout=subprocess.DEVNULL if quiet else None,
|
59 |
-
stderr=subprocess.STDOUT if quiet else None,
|
60 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|