From d7e77b39e8cadf61bc3ccfe44be352ee4a8b518d Mon Sep 17 00:00:00 2001 From: Justin John <34035011+justinjohn0306@users.noreply.github.com> Date: Fri, 13 Oct 2023 08:31:19 +0530 Subject: [PATCH] Add files via upload --- .../utils/__pycache__/audio.cpython-38.pyc | Bin 0 -> 4667 bytes .../utils/__pycache__/croper.cpython-38.pyc | Bin 0 -> 4086 bytes .../__pycache__/face_enhancer.cpython-38.pyc | Bin 0 -> 3677 bytes .../utils/__pycache__/hparams.cpython-38.pyc | Bin 0 -> 2352 bytes .../__pycache__/init_path.cpython-38.pyc | Bin 0 -> 1613 bytes .../__pycache__/paste_pic.cpython-38.pyc | Bin 0 -> 1834 bytes .../__pycache__/preprocess.cpython-38.pyc | Bin 0 -> 5468 bytes .../safetensor_helper.cpython-38.pyc | Bin 0 -> 403 bytes .../utils/__pycache__/videoio.cpython-38.pyc | Bin 0 -> 1422 bytes animation/SadTalker/src/utils/audio.py | 136 ++++++++++++++ animation/SadTalker/src/utils/croper.py | 144 +++++++++++++++ .../SadTalker/src/utils/face_enhancer.py | 123 +++++++++++++ animation/SadTalker/src/utils/hparams.py | 160 +++++++++++++++++ animation/SadTalker/src/utils/init_path.py | 47 +++++ .../SadTalker/src/utils/model2safetensor.py | 141 +++++++++++++++ animation/SadTalker/src/utils/paste_pic.py | 69 +++++++ animation/SadTalker/src/utils/preprocess.py | 170 ++++++++++++++++++ .../SadTalker/src/utils/safetensor_helper.py | 8 + animation/SadTalker/src/utils/text2speech.py | 20 +++ animation/SadTalker/src/utils/videoio.py | 41 +++++ 20 files changed, 1059 insertions(+) create mode 100644 animation/SadTalker/src/utils/__pycache__/audio.cpython-38.pyc create mode 100644 animation/SadTalker/src/utils/__pycache__/croper.cpython-38.pyc create mode 100644 animation/SadTalker/src/utils/__pycache__/face_enhancer.cpython-38.pyc create mode 100644 animation/SadTalker/src/utils/__pycache__/hparams.cpython-38.pyc create mode 100644 animation/SadTalker/src/utils/__pycache__/init_path.cpython-38.pyc create mode 100644 animation/SadTalker/src/utils/__pycache__/paste_pic.cpython-38.pyc create mode 100644 animation/SadTalker/src/utils/__pycache__/preprocess.cpython-38.pyc create mode 100644 animation/SadTalker/src/utils/__pycache__/safetensor_helper.cpython-38.pyc create mode 100644 animation/SadTalker/src/utils/__pycache__/videoio.cpython-38.pyc create mode 100644 animation/SadTalker/src/utils/audio.py create mode 100644 animation/SadTalker/src/utils/croper.py create mode 100644 animation/SadTalker/src/utils/face_enhancer.py create mode 100644 animation/SadTalker/src/utils/hparams.py create mode 100644 animation/SadTalker/src/utils/init_path.py create mode 100644 animation/SadTalker/src/utils/model2safetensor.py create mode 100644 animation/SadTalker/src/utils/paste_pic.py create mode 100644 animation/SadTalker/src/utils/preprocess.py create mode 100644 animation/SadTalker/src/utils/safetensor_helper.py create mode 100644 animation/SadTalker/src/utils/text2speech.py create mode 100644 animation/SadTalker/src/utils/videoio.py diff --git a/animation/SadTalker/src/utils/__pycache__/audio.cpython-38.pyc b/animation/SadTalker/src/utils/__pycache__/audio.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb846544bd9e23679b2b39cdf407438760293842 GIT binary patch literal 4667 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 literal 0 HcmV?d00001 diff --git a/animation/SadTalker/src/utils/__pycache__/croper.cpython-38.pyc b/animation/SadTalker/src/utils/__pycache__/croper.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71efb08c3a2cb262c76d368024052109530b6797 GIT binary patch literal 4086 zcmc&%&2J<}6|bu9`Tn#$w#VMJGn)hn1F=>MB#zl+vn1@YB(egVh^S@M>YeHtx82h- zTixEZtr`w-_JWjh9Lh^|_LRx;WdTfs^IMbtf^**cWy?Ryk z>f>6i%y^jH)TauA~`#xU{r?tYvE+wTpZM&Zzt@%FpJ0jx0vx&6?S zA9>-PuU;E?L3BZG$Ev%%p9EpN-BrVpuhvJ0Y0-6qC`eqlMbk6$=123>F0G$wT=FUp z^%DNZtFM71?1VpOzh2->vt6-ko!FX7{#=a3&)N6cPub7dCoIWnam=OI6Y6`?ddRep zVu_6{&6B)Fte4nvhUEx*CJYxE6K+1^N=sVLM9+eyofOPy#gme@pChW{oX%x(DS9Hu zme@rfZ!nA9X6JHyN^V9zlVeQ^NB8@F(m6|?&eI^->Bum6RPIc^P30&1D%$KsqjgUy z@30f^cq9L7izy$NUFOT8%57d0n|#*l@QyfIx|AH#cBjl{ ztc(Bo_X;qYipi2+L4p;>`ah#pw`uihC4nO^uxhr!^T4s|3|;`9lX-&|f#+qx;3eQV zB@A8$UX&$+SAdsf+2GZL?Q&hhc$Lq^xMryGgsSVBt}ilKZLx7fgKD#JQ&%L%TxQVL z(9Y@^Jv-5v({s83otADQ&xLM1=Qtx8ebHjz&g*&fZt3|i*h)s(hHierWV3~x0a*+9 zOp6_hH=$wrn7`;_yr3JpC1+b~k&PE2&FMuw-)4FN{|UBeFu;Witj+q!`f>YgbU6oq zmXbVrDCi|^T{9kUWn<6&lB>TOc`+$zzKeL!p3=+L7-?OSG^QmzF}|kxNkv~v zs=GDpYOdRQ8Q!)r4z)|s%}ds%-~ZkxajCz=&Mfp2c}`r}Iu_&WhFeJLx-IAB{4-=s z&zu40y-mv+S7eRE{-1W!vMIZsJzjy;3dKTSZ{t2Z7KTOy&gnYjb3`NhXK@plr>k=wF;Mf$S7rdRY!x}$ICm-}`PchB0=(cSY?dUNC{ zZ_w!lp+EAHoy|_1s0-oQQUBqC4?3m*ITx8j{d5K=-01>=YXn{*@G60C5x7a(ouf;0-tICoAB8?{Xc$9-@otlfBPHy{%}XpII0P-by^@JI%!X- zazGU#=$s$;UXJDv5l;MpcaY}d#Ov;EZu5~CXpW8bMj1SCmokF%s}p_0N%=v_ z4^!Sv?Z^8bV*EG@T)cy5?xi`Ek&lK-npzNLR%(r)D8d8$4^yGgs>7?-Z`lijeq?U> zZzGuT4gkwl`IVnl=XLOk=7-i=0R1wb1@9KG+Y7wRH^3?Kvbbrr`A7Wv3tPO-7XUXz z)5_?#`SJ_9S>t4p-RENmEu#>xVtdBtKT{R0{Sy!J4 zUD6d)j#P2XY7zel{JL&}U(=0q{w(!&n?3#%Z9x~!S&`c^JN@Xjt`~KpVbaMeuTHY# zUlfTwH5_!};lAqnoxr?Ptc(4jet>A19llml*9rd#0orP5i7ZVKIoYAgi~7Ez3M{p{ zPj0CdqajrYLlN`9KIehH1Z1Hepz6;TylHwpSGZ-Ue2EpS>$!K0Z=2}N%W zyxmk0mS|R}5%<*l)E4Od5qSgObIw5IdpZFl$n&F zF%5-HU=>^Chl;psbEz1{Y6~cyj={iB?S43XlombJ$I&1Ah-5dwb0`<>4@QUTHBz*2 z-kSwdvmg}}?P)2%iIWV6VXWx%b#CBIwNAzPcjdaDy*%B$k^3Z&{xEi9ujiYmoij^1 w2Y5iNoBcKLCpEm8-IO23EmM&fZ)eeXn_dj@DnPdF&mbKGI37@)R|PQfA2I^ytpET3 literal 0 HcmV?d00001 diff --git a/animation/SadTalker/src/utils/__pycache__/face_enhancer.cpython-38.pyc b/animation/SadTalker/src/utils/__pycache__/face_enhancer.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..067fa249b3b7a4ce3cd2eb7b966c3e7a03ec349a GIT binary patch literal 3677 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 literal 0 HcmV?d00001 diff --git a/animation/SadTalker/src/utils/__pycache__/hparams.cpython-38.pyc b/animation/SadTalker/src/utils/__pycache__/hparams.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f056d2fd3762a8bd5852c493c16d745eb8d9171 GIT binary patch literal 2352 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 0 + if hp.symmetric_mels: + return (2 * hp.max_abs_value) * ((S - hp.min_level_db) / (-hp.min_level_db)) - hp.max_abs_value + else: + return hp.max_abs_value * ((S - hp.min_level_db) / (-hp.min_level_db)) + +def _denormalize(D): + if hp.allow_clipping_in_normalization: + if hp.symmetric_mels: + return (((np.clip(D, -hp.max_abs_value, + hp.max_abs_value) + hp.max_abs_value) * -hp.min_level_db / (2 * hp.max_abs_value)) + + hp.min_level_db) + else: + return ((np.clip(D, 0, hp.max_abs_value) * -hp.min_level_db / hp.max_abs_value) + hp.min_level_db) + + if hp.symmetric_mels: + return (((D + hp.max_abs_value) * -hp.min_level_db / (2 * hp.max_abs_value)) + hp.min_level_db) + else: + return ((D * -hp.min_level_db / hp.max_abs_value) + hp.min_level_db) diff --git a/animation/SadTalker/src/utils/croper.py b/animation/SadTalker/src/utils/croper.py new file mode 100644 index 0000000..3d9a0ac --- /dev/null +++ b/animation/SadTalker/src/utils/croper.py @@ -0,0 +1,144 @@ +import os +import cv2 +import time +import glob +import argparse +import scipy +import numpy as np +from PIL import Image +import torch +from tqdm import tqdm +from itertools import cycle + +from src.face3d.extract_kp_videos_safe import KeypointExtractor +from facexlib.alignment import landmark_98_to_68 + +import numpy as np +from PIL import Image + +class Preprocesser: + def __init__(self, device='cuda'): + self.predictor = KeypointExtractor(device) + + def get_landmark(self, img_np): + """get landmark with dlib + :return: np.array shape=(68, 2) + """ + with torch.no_grad(): + dets = self.predictor.det_net.detect_faces(img_np, 0.97) + + if len(dets) == 0: + return None + det = dets[0] + + img = img_np[int(det[1]):int(det[3]), int(det[0]):int(det[2]), :] + lm = landmark_98_to_68(self.predictor.detector.get_landmarks(img)) # [0] + + #### keypoints to the original location + lm[:,0] += int(det[0]) + lm[:,1] += int(det[1]) + + return lm + + def align_face(self, img, lm, output_size=1024): + """ + :param filepath: str + :return: PIL Image + """ + lm_chin = lm[0: 17] # left-right + lm_eyebrow_left = lm[17: 22] # left-right + lm_eyebrow_right = lm[22: 27] # left-right + lm_nose = lm[27: 31] # top-down + lm_nostrils = lm[31: 36] # top-down + lm_eye_left = lm[36: 42] # left-clockwise + lm_eye_right = lm[42: 48] # left-clockwise + lm_mouth_outer = lm[48: 60] # left-clockwise + lm_mouth_inner = lm[60: 68] # left-clockwise + + # Calculate auxiliary vectors. + eye_left = np.mean(lm_eye_left, axis=0) + eye_right = np.mean(lm_eye_right, axis=0) + eye_avg = (eye_left + eye_right) * 0.5 + eye_to_eye = eye_right - eye_left + mouth_left = lm_mouth_outer[0] + mouth_right = lm_mouth_outer[6] + mouth_avg = (mouth_left + mouth_right) * 0.5 + eye_to_mouth = mouth_avg - eye_avg + + # Choose oriented crop rectangle. + x = eye_to_eye - np.flipud(eye_to_mouth) * [-1, 1] # Addition of binocular difference and double mouth difference + x /= np.hypot(*x) # hypot函数计算直角三角形的斜边长,用斜边长对三角形两条直边做归一化 + x *= max(np.hypot(*eye_to_eye) * 2.0, np.hypot(*eye_to_mouth) * 1.8) # 双眼差和眼嘴差,选较大的作为基准尺度 + y = np.flipud(x) * [-1, 1] + c = eye_avg + eye_to_mouth * 0.1 + quad = np.stack([c - x - y, c - x + y, c + x + y, c + x - y]) # 定义四边形,以面部基准位置为中心上下左右平移得到四个顶点 + qsize = np.hypot(*x) * 2 # 定义四边形的大小(边长),为基准尺度的2倍 + + # Shrink. + # 如果计算出的四边形太大了,就按比例缩小它 + shrink = int(np.floor(qsize / output_size * 0.5)) + if shrink > 1: + rsize = (int(np.rint(float(img.size[0]) / shrink)), int(np.rint(float(img.size[1]) / shrink))) + img = img.resize(rsize, Image.ANTIALIAS) + quad /= shrink + qsize /= shrink + else: + rsize = (int(np.rint(float(img.size[0]))), int(np.rint(float(img.size[1])))) + + # Crop. + border = max(int(np.rint(qsize * 0.1)), 3) + crop = (int(np.floor(min(quad[:, 0]))), int(np.floor(min(quad[:, 1]))), int(np.ceil(max(quad[:, 0]))), + int(np.ceil(max(quad[:, 1])))) + crop = (max(crop[0] - border, 0), max(crop[1] - border, 0), min(crop[2] + border, img.size[0]), + min(crop[3] + border, img.size[1])) + if crop[2] - crop[0] < img.size[0] or crop[3] - crop[1] < img.size[1]: + # img = img.crop(crop) + quad -= crop[0:2] + + # Pad. + pad = (int(np.floor(min(quad[:, 0]))), int(np.floor(min(quad[:, 1]))), int(np.ceil(max(quad[:, 0]))), + int(np.ceil(max(quad[:, 1])))) + pad = (max(-pad[0] + border, 0), max(-pad[1] + border, 0), max(pad[2] - img.size[0] + border, 0), + max(pad[3] - img.size[1] + border, 0)) + # if enable_padding and max(pad) > border - 4: + # pad = np.maximum(pad, int(np.rint(qsize * 0.3))) + # img = np.pad(np.float32(img), ((pad[1], pad[3]), (pad[0], pad[2]), (0, 0)), 'reflect') + # h, w, _ = img.shape + # y, x, _ = np.ogrid[:h, :w, :1] + # mask = np.maximum(1.0 - np.minimum(np.float32(x) / pad[0], np.float32(w - 1 - x) / pad[2]), + # 1.0 - np.minimum(np.float32(y) / pad[1], np.float32(h - 1 - y) / pad[3])) + # blur = qsize * 0.02 + # img += (scipy.ndimage.gaussian_filter(img, [blur, blur, 0]) - img) * np.clip(mask * 3.0 + 1.0, 0.0, 1.0) + # img += (np.median(img, axis=(0, 1)) - img) * np.clip(mask, 0.0, 1.0) + # img = Image.fromarray(np.uint8(np.clip(np.rint(img), 0, 255)), 'RGB') + # quad += pad[:2] + + # Transform. + quad = (quad + 0.5).flatten() + lx = max(min(quad[0], quad[2]), 0) + ly = max(min(quad[1], quad[7]), 0) + rx = min(max(quad[4], quad[6]), img.size[0]) + ry = min(max(quad[3], quad[5]), img.size[0]) + + # Save aligned image. + return rsize, crop, [lx, ly, rx, ry] + + def crop(self, img_np_list, still=False, xsize=512): # first frame for all video + img_np = img_np_list[0] + lm = self.get_landmark(img_np) + + if lm is None: + raise 'can not detect the landmark from source image' + rsize, crop, quad = self.align_face(img=Image.fromarray(img_np), lm=lm, output_size=xsize) + clx, cly, crx, cry = crop + lx, ly, rx, ry = quad + lx, ly, rx, ry = int(lx), int(ly), int(rx), int(ry) + for _i in range(len(img_np_list)): + _inp = img_np_list[_i] + _inp = cv2.resize(_inp, (rsize[0], rsize[1])) + _inp = _inp[cly:cry, clx:crx] + if not still: + _inp = _inp[ly:ry, lx:rx] + img_np_list[_i] = _inp + return img_np_list, crop, quad + diff --git a/animation/SadTalker/src/utils/face_enhancer.py b/animation/SadTalker/src/utils/face_enhancer.py new file mode 100644 index 0000000..15851a1 --- /dev/null +++ b/animation/SadTalker/src/utils/face_enhancer.py @@ -0,0 +1,123 @@ +import os +import torch + +from gfpgan import GFPGANer + +from tqdm import tqdm + +from src.utils.videoio import load_video_to_cv2 + +import cv2 + + +class GeneratorWithLen(object): + """ From https://stackoverflow.com/a/7460929 """ + + def __init__(self, gen, length): + self.gen = gen + self.length = length + + def __len__(self): + return self.length + + def __iter__(self): + return self.gen + +def enhancer_list(images, method='gfpgan', bg_upsampler='realesrgan'): + gen = enhancer_generator_no_len(images, method=method, bg_upsampler=bg_upsampler) + return list(gen) + +def enhancer_generator_with_len(images, method='gfpgan', bg_upsampler='realesrgan'): + """ Provide a generator with a __len__ method so that it can passed to functions that + call len()""" + + if os.path.isfile(images): # handle video to images + # TODO: Create a generator version of load_video_to_cv2 + images = load_video_to_cv2(images) + + gen = enhancer_generator_no_len(images, method=method, bg_upsampler=bg_upsampler) + gen_with_len = GeneratorWithLen(gen, len(images)) + return gen_with_len + +def enhancer_generator_no_len(images, method='gfpgan', bg_upsampler='realesrgan'): + """ Provide a generator function so that all of the enhanced images don't need + to be stored in memory at the same time. This can save tons of RAM compared to + the enhancer function. """ + + print('face enhancer....') + if not isinstance(images, list) and os.path.isfile(images): # handle video to images + images = load_video_to_cv2(images) + + # ------------------------ set up GFPGAN restorer ------------------------ + if method == 'gfpgan': + arch = 'clean' + channel_multiplier = 2 + model_name = 'GFPGANv1.4' + url = 'https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth' + elif method == 'RestoreFormer': + arch = 'RestoreFormer' + channel_multiplier = 2 + model_name = 'RestoreFormer' + url = 'https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/RestoreFormer.pth' + elif method == 'codeformer': # TODO: + arch = 'CodeFormer' + channel_multiplier = 2 + model_name = 'CodeFormer' + url = 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth' + else: + raise ValueError(f'Wrong model version {method}.') + + + # ------------------------ set up background upsampler ------------------------ + if bg_upsampler == 'realesrgan': + if not torch.cuda.is_available(): # CPU + import warnings + warnings.warn('The unoptimized RealESRGAN is slow on CPU. We do not use it. ' + 'If you really want to use it, please modify the corresponding codes.') + bg_upsampler = None + else: + from basicsr.archs.rrdbnet_arch import RRDBNet + from realesrgan import RealESRGANer + model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=2) + bg_upsampler = RealESRGANer( + scale=2, + model_path='https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth', + model=model, + tile=400, + tile_pad=10, + pre_pad=0, + half=True) # need to set False in CPU mode + else: + bg_upsampler = None + + # determine model paths + model_path = os.path.join('gfpgan/weights', model_name + '.pth') + + if not os.path.isfile(model_path): + model_path = os.path.join('checkpoints', model_name + '.pth') + + if not os.path.isfile(model_path): + # download pre-trained models from url + model_path = url + + restorer = GFPGANer( + model_path=model_path, + upscale=2, + arch=arch, + channel_multiplier=channel_multiplier, + bg_upsampler=bg_upsampler) + + # ------------------------ restore ------------------------ + for idx in tqdm(range(len(images)), 'Face Enhancer:'): + + img = cv2.cvtColor(images[idx], cv2.COLOR_RGB2BGR) + + # restore faces and background if necessary + cropped_faces, restored_faces, r_img = restorer.enhance( + img, + has_aligned=False, + only_center_face=False, + paste_back=True) + + r_img = cv2.cvtColor(r_img, cv2.COLOR_BGR2RGB) + yield r_img diff --git a/animation/SadTalker/src/utils/hparams.py b/animation/SadTalker/src/utils/hparams.py new file mode 100644 index 0000000..743c5c7 --- /dev/null +++ b/animation/SadTalker/src/utils/hparams.py @@ -0,0 +1,160 @@ +from glob import glob +import os + +class HParams: + def __init__(self, **kwargs): + self.data = {} + + for key, value in kwargs.items(): + self.data[key] = value + + def __getattr__(self, key): + if key not in self.data: + raise AttributeError("'HParams' object has no attribute %s" % key) + return self.data[key] + + def set_hparam(self, key, value): + self.data[key] = value + + +# Default hyperparameters +hparams = HParams( + num_mels=80, # Number of mel-spectrogram channels and local conditioning dimensionality + # network + rescale=True, # Whether to rescale audio prior to preprocessing + rescaling_max=0.9, # Rescaling value + + # Use LWS (https://github.com/Jonathan-LeRoux/lws) for STFT and phase reconstruction + # It"s preferred to set True to use with https://github.com/r9y9/wavenet_vocoder + # Does not work if n_ffit is not multiple of hop_size!! + use_lws=False, + + n_fft=800, # Extra window size is filled with 0 paddings to match this parameter + hop_size=200, # For 16000Hz, 200 = 12.5 ms (0.0125 * sample_rate) + win_size=800, # For 16000Hz, 800 = 50 ms (If None, win_size = n_fft) (0.05 * sample_rate) + sample_rate=16000, # 16000Hz (corresponding to librispeech) (sox --i ) + + frame_shift_ms=None, # Can replace hop_size parameter. (Recommended: 12.5) + + # Mel and Linear spectrograms normalization/scaling and clipping + signal_normalization=True, + # Whether to normalize mel spectrograms to some predefined range (following below parameters) + allow_clipping_in_normalization=True, # Only relevant if mel_normalization = True + symmetric_mels=True, + # Whether to scale the data to be symmetric around 0. (Also multiplies the output range by 2, + # faster and cleaner convergence) + max_abs_value=4., + # max absolute value of data. If symmetric, data will be [-max, max] else [0, max] (Must not + # be too big to avoid gradient explosion, + # not too small for fast convergence) + # Contribution by @begeekmyfriend + # Spectrogram Pre-Emphasis (Lfilter: Reduce spectrogram noise and helps model certitude + # levels. Also allows for better G&L phase reconstruction) + preemphasize=True, # whether to apply filter + preemphasis=0.97, # filter coefficient. + + # Limits + min_level_db=-100, + ref_level_db=20, + fmin=55, + # Set this to 55 if your speaker is male! if female, 95 should help taking off noise. (To + # test depending on dataset. Pitch info: male~[65, 260], female~[100, 525]) + fmax=7600, # To be increased/reduced depending on data. + + ###################### Our training parameters ################################# + img_size=96, + fps=25, + + batch_size=16, + initial_learning_rate=1e-4, + nepochs=300000, ### ctrl + c, stop whenever eval loss is consistently greater than train loss for ~10 epochs + num_workers=20, + checkpoint_interval=3000, + eval_interval=3000, + writer_interval=300, + save_optimizer_state=True, + + syncnet_wt=0.0, # is initially zero, will be set automatically to 0.03 later. Leads to faster convergence. + syncnet_batch_size=64, + syncnet_lr=1e-4, + syncnet_eval_interval=1000, + syncnet_checkpoint_interval=10000, + + disc_wt=0.07, + disc_initial_learning_rate=1e-4, +) + + + +# Default hyperparameters +hparamsdebug = HParams( + num_mels=80, # Number of mel-spectrogram channels and local conditioning dimensionality + # network + rescale=True, # Whether to rescale audio prior to preprocessing + rescaling_max=0.9, # Rescaling value + + # Use LWS (https://github.com/Jonathan-LeRoux/lws) for STFT and phase reconstruction + # It"s preferred to set True to use with https://github.com/r9y9/wavenet_vocoder + # Does not work if n_ffit is not multiple of hop_size!! + use_lws=False, + + n_fft=800, # Extra window size is filled with 0 paddings to match this parameter + hop_size=200, # For 16000Hz, 200 = 12.5 ms (0.0125 * sample_rate) + win_size=800, # For 16000Hz, 800 = 50 ms (If None, win_size = n_fft) (0.05 * sample_rate) + sample_rate=16000, # 16000Hz (corresponding to librispeech) (sox --i ) + + frame_shift_ms=None, # Can replace hop_size parameter. (Recommended: 12.5) + + # Mel and Linear spectrograms normalization/scaling and clipping + signal_normalization=True, + # Whether to normalize mel spectrograms to some predefined range (following below parameters) + allow_clipping_in_normalization=True, # Only relevant if mel_normalization = True + symmetric_mels=True, + # Whether to scale the data to be symmetric around 0. (Also multiplies the output range by 2, + # faster and cleaner convergence) + max_abs_value=4., + # max absolute value of data. If symmetric, data will be [-max, max] else [0, max] (Must not + # be too big to avoid gradient explosion, + # not too small for fast convergence) + # Contribution by @begeekmyfriend + # Spectrogram Pre-Emphasis (Lfilter: Reduce spectrogram noise and helps model certitude + # levels. Also allows for better G&L phase reconstruction) + preemphasize=True, # whether to apply filter + preemphasis=0.97, # filter coefficient. + + # Limits + min_level_db=-100, + ref_level_db=20, + fmin=55, + # Set this to 55 if your speaker is male! if female, 95 should help taking off noise. (To + # test depending on dataset. Pitch info: male~[65, 260], female~[100, 525]) + fmax=7600, # To be increased/reduced depending on data. + + ###################### Our training parameters ################################# + img_size=96, + fps=25, + + batch_size=2, + initial_learning_rate=1e-3, + nepochs=100000, ### ctrl + c, stop whenever eval loss is consistently greater than train loss for ~10 epochs + num_workers=0, + checkpoint_interval=10000, + eval_interval=10, + writer_interval=5, + save_optimizer_state=True, + + syncnet_wt=0.0, # is initially zero, will be set automatically to 0.03 later. Leads to faster convergence. + syncnet_batch_size=64, + syncnet_lr=1e-4, + syncnet_eval_interval=10000, + syncnet_checkpoint_interval=10000, + + disc_wt=0.07, + disc_initial_learning_rate=1e-4, +) + + +def hparams_debug_string(): + values = hparams.values() + hp = [" %s: %s" % (name, values[name]) for name in sorted(values) if name != "sentences"] + return "Hyperparameters:\n" + "\n".join(hp) diff --git a/animation/SadTalker/src/utils/init_path.py b/animation/SadTalker/src/utils/init_path.py new file mode 100644 index 0000000..5f38d11 --- /dev/null +++ b/animation/SadTalker/src/utils/init_path.py @@ -0,0 +1,47 @@ +import os +import glob + +def init_path(checkpoint_dir, config_dir, size=512, old_version=False, preprocess='crop'): + + if old_version: + #### load all the checkpoint of `pth` + sadtalker_paths = { + 'wav2lip_checkpoint' : os.path.join(checkpoint_dir, 'wav2lip.pth'), + 'audio2pose_checkpoint' : os.path.join(checkpoint_dir, 'auido2pose_00140-model.pth'), + 'audio2exp_checkpoint' : os.path.join(checkpoint_dir, 'auido2exp_00300-model.pth'), + 'free_view_checkpoint' : os.path.join(checkpoint_dir, 'facevid2vid_00189-model.pth.tar'), + 'path_of_net_recon_model' : os.path.join(checkpoint_dir, 'epoch_20.pth') + } + + use_safetensor = False + elif len(glob.glob(os.path.join(checkpoint_dir, '*.safetensors'))): + print('using safetensor as default') + sadtalker_paths = { + "checkpoint":os.path.join(checkpoint_dir, 'SadTalker_V0.0.2_'+str(size)+'.safetensors'), + } + use_safetensor = True + else: + print("WARNING: The new version of the model will be updated by safetensor, you may need to download it mannully. We run the old version of the checkpoint this time!") + use_safetensor = False + + sadtalker_paths = { + 'wav2lip_checkpoint' : os.path.join(checkpoint_dir, 'wav2lip.pth'), + 'audio2pose_checkpoint' : os.path.join(checkpoint_dir, 'auido2pose_00140-model.pth'), + 'audio2exp_checkpoint' : os.path.join(checkpoint_dir, 'auido2exp_00300-model.pth'), + 'free_view_checkpoint' : os.path.join(checkpoint_dir, 'facevid2vid_00189-model.pth.tar'), + 'path_of_net_recon_model' : os.path.join(checkpoint_dir, 'epoch_20.pth') + } + + sadtalker_paths['dir_of_BFM_fitting'] = os.path.join(config_dir) # , 'BFM_Fitting' + sadtalker_paths['audio2pose_yaml_path'] = os.path.join(config_dir, 'auido2pose.yaml') + sadtalker_paths['audio2exp_yaml_path'] = os.path.join(config_dir, 'auido2exp.yaml') + sadtalker_paths['use_safetensor'] = use_safetensor # os.path.join(config_dir, 'auido2exp.yaml') + + if 'full' in preprocess: + sadtalker_paths['mappingnet_checkpoint'] = os.path.join(checkpoint_dir, 'mapping_00109-model.pth.tar') + sadtalker_paths['facerender_yaml'] = os.path.join(config_dir, 'facerender_still.yaml') + else: + sadtalker_paths['mappingnet_checkpoint'] = os.path.join(checkpoint_dir, 'mapping_00229-model.pth.tar') + sadtalker_paths['facerender_yaml'] = os.path.join(config_dir, 'facerender.yaml') + + return sadtalker_paths \ No newline at end of file diff --git a/animation/SadTalker/src/utils/model2safetensor.py b/animation/SadTalker/src/utils/model2safetensor.py new file mode 100644 index 0000000..50c4850 --- /dev/null +++ b/animation/SadTalker/src/utils/model2safetensor.py @@ -0,0 +1,141 @@ +import torch +import yaml +import os + +import safetensors +from safetensors.torch import save_file +from yacs.config import CfgNode as CN +import sys + +sys.path.append('/apdcephfs/private_shadowcun/SadTalker') + +from src.face3d.models import networks + +from src.facerender.modules.keypoint_detector import HEEstimator, KPDetector +from src.facerender.modules.mapping import MappingNet +from src.facerender.modules.generator import OcclusionAwareGenerator, OcclusionAwareSPADEGenerator + +from src.audio2pose_models.audio2pose import Audio2Pose +from src.audio2exp_models.networks import SimpleWrapperV2 +from src.test_audio2coeff import load_cpk + +size = 256 +############ face vid2vid +config_path = os.path.join('src', 'config', 'facerender.yaml') +current_root_path = '.' + +path_of_net_recon_model = os.path.join(current_root_path, 'checkpoints', 'epoch_20.pth') +net_recon = networks.define_net_recon(net_recon='resnet50', use_last_fc=False, init_path='') +checkpoint = torch.load(path_of_net_recon_model, map_location='cpu') +net_recon.load_state_dict(checkpoint['net_recon']) + +with open(config_path) as f: + config = yaml.safe_load(f) + +generator = OcclusionAwareSPADEGenerator(**config['model_params']['generator_params'], + **config['model_params']['common_params']) +kp_extractor = KPDetector(**config['model_params']['kp_detector_params'], + **config['model_params']['common_params']) +he_estimator = HEEstimator(**config['model_params']['he_estimator_params'], + **config['model_params']['common_params']) +mapping = MappingNet(**config['model_params']['mapping_params']) + +def load_cpk_facevid2vid(checkpoint_path, generator=None, discriminator=None, + kp_detector=None, he_estimator=None, optimizer_generator=None, + optimizer_discriminator=None, optimizer_kp_detector=None, + optimizer_he_estimator=None, device="cpu"): + + checkpoint = torch.load(checkpoint_path, map_location=torch.device(device)) + if generator is not None: + generator.load_state_dict(checkpoint['generator']) + if kp_detector is not None: + kp_detector.load_state_dict(checkpoint['kp_detector']) + if he_estimator is not None: + he_estimator.load_state_dict(checkpoint['he_estimator']) + if discriminator is not None: + try: + discriminator.load_state_dict(checkpoint['discriminator']) + except: + print ('No discriminator in the state-dict. Dicriminator will be randomly initialized') + if optimizer_generator is not None: + optimizer_generator.load_state_dict(checkpoint['optimizer_generator']) + if optimizer_discriminator is not None: + try: + optimizer_discriminator.load_state_dict(checkpoint['optimizer_discriminator']) + except RuntimeError as e: + print ('No discriminator optimizer in the state-dict. Optimizer will be not initialized') + if optimizer_kp_detector is not None: + optimizer_kp_detector.load_state_dict(checkpoint['optimizer_kp_detector']) + if optimizer_he_estimator is not None: + optimizer_he_estimator.load_state_dict(checkpoint['optimizer_he_estimator']) + + return checkpoint['epoch'] + + +def load_cpk_facevid2vid_safetensor(checkpoint_path, generator=None, + kp_detector=None, he_estimator=None, + device="cpu"): + + checkpoint = safetensors.torch.load_file(checkpoint_path) + + if generator is not None: + x_generator = {} + for k,v in checkpoint.items(): + if 'generator' in k: + x_generator[k.replace('generator.', '')] = v + generator.load_state_dict(x_generator) + if kp_detector is not None: + x_generator = {} + for k,v in checkpoint.items(): + if 'kp_extractor' in k: + x_generator[k.replace('kp_extractor.', '')] = v + kp_detector.load_state_dict(x_generator) + if he_estimator is not None: + x_generator = {} + for k,v in checkpoint.items(): + if 'he_estimator' in k: + x_generator[k.replace('he_estimator.', '')] = v + he_estimator.load_state_dict(x_generator) + + return None + +free_view_checkpoint = '/apdcephfs/private_shadowcun/SadTalker/checkpoints/facevid2vid_'+str(size)+'-model.pth.tar' +load_cpk_facevid2vid(free_view_checkpoint, kp_detector=kp_extractor, generator=generator, he_estimator=he_estimator) + +wav2lip_checkpoint = os.path.join(current_root_path, 'checkpoints', 'wav2lip.pth') + +audio2pose_checkpoint = os.path.join(current_root_path, 'checkpoints', 'auido2pose_00140-model.pth') +audio2pose_yaml_path = os.path.join(current_root_path, 'src', 'config', 'auido2pose.yaml') + +audio2exp_checkpoint = os.path.join(current_root_path, 'checkpoints', 'auido2exp_00300-model.pth') +audio2exp_yaml_path = os.path.join(current_root_path, 'src', 'config', 'auido2exp.yaml') + +fcfg_pose = open(audio2pose_yaml_path) +cfg_pose = CN.load_cfg(fcfg_pose) +cfg_pose.freeze() +audio2pose_model = Audio2Pose(cfg_pose, wav2lip_checkpoint) +audio2pose_model.eval() +load_cpk(audio2pose_checkpoint, model=audio2pose_model, device='cpu') + +# load audio2exp_model +netG = SimpleWrapperV2() +netG.eval() +load_cpk(audio2exp_checkpoint, model=netG, device='cpu') + +class SadTalker(torch.nn.Module): + def __init__(self, kp_extractor, generator, netG, audio2pose, face_3drecon): + super(SadTalker, self).__init__() + self.kp_extractor = kp_extractor + self.generator = generator + self.audio2exp = netG + self.audio2pose = audio2pose + self.face_3drecon = face_3drecon + + +model = SadTalker(kp_extractor, generator, netG, audio2pose_model, net_recon) + +# here, we want to convert it to safetensor +save_file(model.state_dict(), "checkpoints/SadTalker_V0.0.2_"+str(size)+".safetensors") + +### test +load_cpk_facevid2vid_safetensor('checkpoints/SadTalker_V0.0.2_'+str(size)+'.safetensors', kp_detector=kp_extractor, generator=generator, he_estimator=None) \ No newline at end of file diff --git a/animation/SadTalker/src/utils/paste_pic.py b/animation/SadTalker/src/utils/paste_pic.py new file mode 100644 index 0000000..f9989e2 --- /dev/null +++ b/animation/SadTalker/src/utils/paste_pic.py @@ -0,0 +1,69 @@ +import cv2, os +import numpy as np +from tqdm import tqdm +import uuid + +from src.utils.videoio import save_video_with_watermark + +def paste_pic(video_path, pic_path, crop_info, new_audio_path, full_video_path, extended_crop=False): + + if not os.path.isfile(pic_path): + raise ValueError('pic_path must be a valid path to video/image file') + elif pic_path.split('.')[-1] in ['jpg', 'png', 'jpeg']: + # loader for first frame + full_img = cv2.imread(pic_path) + else: + # loader for videos + video_stream = cv2.VideoCapture(pic_path) + fps = video_stream.get(cv2.CAP_PROP_FPS) + full_frames = [] + while 1: + still_reading, frame = video_stream.read() + if not still_reading: + video_stream.release() + break + break + full_img = frame + frame_h = full_img.shape[0] + frame_w = full_img.shape[1] + + video_stream = cv2.VideoCapture(video_path) + fps = video_stream.get(cv2.CAP_PROP_FPS) + crop_frames = [] + while 1: + still_reading, frame = video_stream.read() + if not still_reading: + video_stream.release() + break + crop_frames.append(frame) + + if len(crop_info) != 3: + print("you didn't crop the image") + return + else: + r_w, r_h = crop_info[0] + clx, cly, crx, cry = crop_info[1] + lx, ly, rx, ry = crop_info[2] + lx, ly, rx, ry = int(lx), int(ly), int(rx), int(ry) + # oy1, oy2, ox1, ox2 = cly+ly, cly+ry, clx+lx, clx+rx + # oy1, oy2, ox1, ox2 = cly+ly, cly+ry, clx+lx, clx+rx + + if extended_crop: + oy1, oy2, ox1, ox2 = cly, cry, clx, crx + else: + oy1, oy2, ox1, ox2 = cly+ly, cly+ry, clx+lx, clx+rx + + tmp_path = str(uuid.uuid4())+'.mp4' + out_tmp = cv2.VideoWriter(tmp_path, cv2.VideoWriter_fourcc(*'MP4V'), fps, (frame_w, frame_h)) + for crop_frame in tqdm(crop_frames, 'seamlessClone:'): + p = cv2.resize(crop_frame.astype(np.uint8), (ox2-ox1, oy2 - oy1)) + + mask = 255*np.ones(p.shape, p.dtype) + location = ((ox1+ox2) // 2, (oy1+oy2) // 2) + gen_img = cv2.seamlessClone(p, full_img, mask, location, cv2.NORMAL_CLONE) + out_tmp.write(gen_img) + + out_tmp.release() + + save_video_with_watermark(tmp_path, new_audio_path, full_video_path, watermark=False) + os.remove(tmp_path) diff --git a/animation/SadTalker/src/utils/preprocess.py b/animation/SadTalker/src/utils/preprocess.py new file mode 100644 index 0000000..0f784e6 --- /dev/null +++ b/animation/SadTalker/src/utils/preprocess.py @@ -0,0 +1,170 @@ +import numpy as np +import cv2, os, sys, torch +from tqdm import tqdm +from PIL import Image + +# 3dmm extraction +import safetensors +import safetensors.torch +from src.face3d.util.preprocess import align_img +from src.face3d.util.load_mats import load_lm3d +from src.face3d.models import networks + +from scipy.io import loadmat, savemat +from src.utils.croper import Preprocesser + + +import warnings + +from src.utils.safetensor_helper import load_x_from_safetensor +warnings.filterwarnings("ignore") + +def split_coeff(coeffs): + """ + Return: + coeffs_dict -- a dict of torch.tensors + + Parameters: + coeffs -- torch.tensor, size (B, 256) + """ + id_coeffs = coeffs[:, :80] + exp_coeffs = coeffs[:, 80: 144] + tex_coeffs = coeffs[:, 144: 224] + angles = coeffs[:, 224: 227] + gammas = coeffs[:, 227: 254] + translations = coeffs[:, 254:] + return { + 'id': id_coeffs, + 'exp': exp_coeffs, + 'tex': tex_coeffs, + 'angle': angles, + 'gamma': gammas, + 'trans': translations + } + + +class CropAndExtract(): + def __init__(self, sadtalker_path, device): + + self.propress = Preprocesser(device) + self.net_recon = networks.define_net_recon(net_recon='resnet50', use_last_fc=False, init_path='').to(device) + + if sadtalker_path['use_safetensor']: + checkpoint = safetensors.torch.load_file(sadtalker_path['checkpoint']) + self.net_recon.load_state_dict(load_x_from_safetensor(checkpoint, 'face_3drecon')) + else: + checkpoint = torch.load(sadtalker_path['path_of_net_recon_model'], map_location=torch.device(device)) + self.net_recon.load_state_dict(checkpoint['net_recon']) + + self.net_recon.eval() + self.lm3d_std = load_lm3d(sadtalker_path['dir_of_BFM_fitting']) + self.device = device + + def generate(self, input_path, save_dir, crop_or_resize='crop', source_image_flag=False, pic_size=256): + + pic_name = os.path.splitext(os.path.split(input_path)[-1])[0] + + landmarks_path = os.path.join(save_dir, pic_name+'_landmarks.txt') + coeff_path = os.path.join(save_dir, pic_name+'.mat') + png_path = os.path.join(save_dir, pic_name+'.png') + + #load input + if not os.path.isfile(input_path): + raise ValueError('input_path must be a valid path to video/image file') + elif input_path.split('.')[-1] in ['jpg', 'png', 'jpeg']: + # loader for first frame + full_frames = [cv2.imread(input_path)] + fps = 25 + else: + # loader for videos + video_stream = cv2.VideoCapture(input_path) + fps = video_stream.get(cv2.CAP_PROP_FPS) + full_frames = [] + while 1: + still_reading, frame = video_stream.read() + if not still_reading: + video_stream.release() + break + full_frames.append(frame) + if source_image_flag: + break + + x_full_frames= [cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) for frame in full_frames] + + #### crop images as the + if 'crop' in crop_or_resize.lower(): # default crop + x_full_frames, crop, quad = self.propress.crop(x_full_frames, still=True if 'ext' in crop_or_resize.lower() else False, xsize=512) + clx, cly, crx, cry = crop + lx, ly, rx, ry = quad + lx, ly, rx, ry = int(lx), int(ly), int(rx), int(ry) + oy1, oy2, ox1, ox2 = cly+ly, cly+ry, clx+lx, clx+rx + crop_info = ((ox2 - ox1, oy2 - oy1), crop, quad) + elif 'full' in crop_or_resize.lower(): + x_full_frames, crop, quad = self.propress.crop(x_full_frames, still=True if 'ext' in crop_or_resize.lower() else False, xsize=512) + clx, cly, crx, cry = crop + lx, ly, rx, ry = quad + lx, ly, rx, ry = int(lx), int(ly), int(rx), int(ry) + oy1, oy2, ox1, ox2 = cly+ly, cly+ry, clx+lx, clx+rx + crop_info = ((ox2 - ox1, oy2 - oy1), crop, quad) + else: # resize mode + oy1, oy2, ox1, ox2 = 0, x_full_frames[0].shape[0], 0, x_full_frames[0].shape[1] + crop_info = ((ox2 - ox1, oy2 - oy1), None, None) + + frames_pil = [Image.fromarray(cv2.resize(frame,(pic_size, pic_size))) for frame in x_full_frames] + if len(frames_pil) == 0: + print('No face is detected in the input file') + return None, None + + # save crop info + for frame in frames_pil: + cv2.imwrite(png_path, cv2.cvtColor(np.array(frame), cv2.COLOR_RGB2BGR)) + + # 2. get the landmark according to the detected face. + if not os.path.isfile(landmarks_path): + lm = self.propress.predictor.extract_keypoint(frames_pil, landmarks_path) + else: + print(' Using saved landmarks.') + lm = np.loadtxt(landmarks_path).astype(np.float32) + lm = lm.reshape([len(x_full_frames), -1, 2]) + + if not os.path.isfile(coeff_path): + # load 3dmm paramter generator from Deep3DFaceRecon_pytorch + video_coeffs, full_coeffs = [], [] + for idx in tqdm(range(len(frames_pil)), desc='3DMM Extraction In Video:'): + frame = frames_pil[idx] + W,H = frame.size + lm1 = lm[idx].reshape([-1, 2]) + + if np.mean(lm1) == -1: + lm1 = (self.lm3d_std[:, :2]+1)/2. + lm1 = np.concatenate( + [lm1[:, :1]*W, lm1[:, 1:2]*H], 1 + ) + else: + lm1[:, -1] = H - 1 - lm1[:, -1] + + trans_params, im1, lm1, _ = align_img(frame, lm1, self.lm3d_std) + + trans_params = np.array([float(item) for item in np.hsplit(trans_params, 5)]).astype(np.float32) + im_t = torch.tensor(np.array(im1)/255., dtype=torch.float32).permute(2, 0, 1).to(self.device).unsqueeze(0) + + with torch.no_grad(): + full_coeff = self.net_recon(im_t) + coeffs = split_coeff(full_coeff) + + pred_coeff = {key:coeffs[key].cpu().numpy() for key in coeffs} + + pred_coeff = np.concatenate([ + pred_coeff['exp'], + pred_coeff['angle'], + pred_coeff['trans'], + trans_params[2:][None], + ], 1) + video_coeffs.append(pred_coeff) + full_coeffs.append(full_coeff.cpu().numpy()) + + semantic_npy = np.array(video_coeffs)[:,0] + + savemat(coeff_path, {'coeff_3dmm': semantic_npy, 'full_3dmm': np.array(full_coeffs)[0]}) + + return coeff_path, png_path, crop_info diff --git a/animation/SadTalker/src/utils/safetensor_helper.py b/animation/SadTalker/src/utils/safetensor_helper.py new file mode 100644 index 0000000..3cdbdd2 --- /dev/null +++ b/animation/SadTalker/src/utils/safetensor_helper.py @@ -0,0 +1,8 @@ + + +def load_x_from_safetensor(checkpoint, key): + x_generator = {} + for k,v in checkpoint.items(): + if key in k: + x_generator[k.replace(key+'.', '')] = v + return x_generator \ No newline at end of file diff --git a/animation/SadTalker/src/utils/text2speech.py b/animation/SadTalker/src/utils/text2speech.py new file mode 100644 index 0000000..a9cd9f5 --- /dev/null +++ b/animation/SadTalker/src/utils/text2speech.py @@ -0,0 +1,20 @@ +import os +import tempfile +from TTS.api import TTS + + +class TTSTalker(): + def __init__(self) -> None: + model_name = TTS().list_models()[0] + self.tts = TTS(model_name) + + def test(self, text, language='en'): + + tempf = tempfile.NamedTemporaryFile( + delete = False, + suffix = ('.'+'wav'), + ) + + self.tts.tts_to_file(text, speaker=self.tts.speakers[0], language=language, file_path=tempf.name) + + return tempf.name diff --git a/animation/SadTalker/src/utils/videoio.py b/animation/SadTalker/src/utils/videoio.py new file mode 100644 index 0000000..08bfbdd --- /dev/null +++ b/animation/SadTalker/src/utils/videoio.py @@ -0,0 +1,41 @@ +import shutil +import uuid + +import os + +import cv2 + +def load_video_to_cv2(input_path): + video_stream = cv2.VideoCapture(input_path) + fps = video_stream.get(cv2.CAP_PROP_FPS) + full_frames = [] + while 1: + still_reading, frame = video_stream.read() + if not still_reading: + video_stream.release() + break + full_frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) + return full_frames + +def save_video_with_watermark(video, audio, save_path, watermark=False): + temp_file = str(uuid.uuid4())+'.mp4' + cmd = r'ffmpeg -y -hide_banner -loglevel error -i "%s" -i "%s" -vcodec copy "%s"' % (video, audio, temp_file) + os.system(cmd) + + if watermark is False: + shutil.move(temp_file, save_path) + else: + # watermark + try: + ##### check if stable-diffusion-webui + import webui + from modules import paths + watarmark_path = paths.script_path+"/extensions/SadTalker/docs/sadtalker_logo.png" + except: + # get the root path of sadtalker. + dir_path = os.path.dirname(os.path.realpath(__file__)) + watarmark_path = dir_path+"/../../docs/sadtalker_logo.png" + + cmd = r'ffmpeg -y -hide_banner -loglevel error -i "%s" -i "%s" -filter_complex "[1]scale=100:-1[wm];[0][wm]overlay=(main_w-overlay_w)-10:10" "%s"' % (temp_file, watarmark_path, save_path) + os.system(cmd) + os.remove(temp_file) \ No newline at end of file