LatentSync / latentsync /data /syncnet_dataset.py
Francke's picture
t
24c345c
# Copyright (c) 2024 Bytedance Ltd. and/or its affiliates
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import numpy as np
from torch.utils.data import Dataset
import torch
import random
from ..utils.util import gather_video_paths_recursively
from ..utils.image_processor import ImageProcessor
from ..utils.audio import melspectrogram
import math
from decord import AudioReader, VideoReader, cpu
class SyncNetDataset(Dataset):
def __init__(self, data_dir: str, fileslist: str, config):
if fileslist != "":
with open(fileslist) as file:
self.video_paths = [line.rstrip() for line in file]
elif data_dir != "":
self.video_paths = gather_video_paths_recursively(data_dir)
else:
raise ValueError("data_dir and fileslist cannot be both empty")
self.resolution = config.data.resolution
self.num_frames = config.data.num_frames
self.mel_window_length = math.ceil(self.num_frames / 5 * 16)
self.audio_sample_rate = config.data.audio_sample_rate
self.video_fps = config.data.video_fps
self.audio_samples_length = int(
config.data.audio_sample_rate // config.data.video_fps * config.data.num_frames
)
self.image_processor = ImageProcessor(resolution=config.data.resolution, mask="half")
self.audio_mel_cache_dir = config.data.audio_mel_cache_dir
os.makedirs(self.audio_mel_cache_dir, exist_ok=True)
def __len__(self):
return len(self.video_paths)
def read_audio(self, video_path: str):
ar = AudioReader(video_path, ctx=cpu(self.worker_id), sample_rate=self.audio_sample_rate)
original_mel = melspectrogram(ar[:].asnumpy().squeeze(0))
return torch.from_numpy(original_mel)
def crop_audio_window(self, original_mel, start_index):
start_idx = int(80.0 * (start_index / float(self.video_fps)))
end_idx = start_idx + self.mel_window_length
return original_mel[:, start_idx:end_idx].unsqueeze(0)
def get_frames(self, video_reader: VideoReader):
total_num_frames = len(video_reader)
start_idx = random.randint(0, total_num_frames - self.num_frames)
frames_index = np.arange(start_idx, start_idx + self.num_frames, dtype=int)
while True:
wrong_start_idx = random.randint(0, total_num_frames - self.num_frames)
# wrong_start_idx = random.randint(
# max(0, start_idx - 25), min(total_num_frames - self.num_frames, start_idx + 25)
# )
if wrong_start_idx == start_idx:
continue
# if wrong_start_idx >= start_idx - self.num_frames and wrong_start_idx <= start_idx + self.num_frames:
# continue
wrong_frames_index = np.arange(wrong_start_idx, wrong_start_idx + self.num_frames, dtype=int)
break
frames = video_reader.get_batch(frames_index).asnumpy()
wrong_frames = video_reader.get_batch(wrong_frames_index).asnumpy()
return frames, wrong_frames, start_idx
def worker_init_fn(self, worker_id):
# Initialize the face mesh object in each worker process,
# because the face mesh object cannot be called in subprocesses
self.worker_id = worker_id
# setattr(self, f"image_processor_{worker_id}", ImageProcessor(self.resolution, self.mask))
def __getitem__(self, idx):
# image_processor = getattr(self, f"image_processor_{self.worker_id}")
while True:
try:
idx = random.randint(0, len(self) - 1)
# Get video file path
video_path = self.video_paths[idx]
vr = VideoReader(video_path, ctx=cpu(self.worker_id))
if len(vr) < 2 * self.num_frames:
continue
frames, wrong_frames, start_idx = self.get_frames(vr)
mel_cache_path = os.path.join(
self.audio_mel_cache_dir, os.path.basename(video_path).replace(".mp4", "_mel.pt")
)
if os.path.isfile(mel_cache_path):
try:
original_mel = torch.load(mel_cache_path)
except Exception as e:
print(f"{type(e).__name__} - {e} - {mel_cache_path}")
os.remove(mel_cache_path)
original_mel = self.read_audio(video_path)
torch.save(original_mel, mel_cache_path)
else:
original_mel = self.read_audio(video_path)
torch.save(original_mel, mel_cache_path)
mel = self.crop_audio_window(original_mel, start_idx)
if mel.shape[-1] != self.mel_window_length:
continue
if random.choice([True, False]):
y = torch.ones(1).float()
chosen_frames = frames
else:
y = torch.zeros(1).float()
chosen_frames = wrong_frames
chosen_frames = self.image_processor.process_images(chosen_frames)
# chosen_frames, _, _ = image_processor.prepare_masks_and_masked_images(
# chosen_frames, affine_transform=True
# )
vr.seek(0) # avoid memory leak
break
except Exception as e: # Handle the exception of face not detcted
print(f"{type(e).__name__} - {e} - {video_path}")
if "vr" in locals():
vr.seek(0) # avoid memory leak
sample = dict(frames=chosen_frames, audio_samples=mel, y=y)
return sample