''' sudo apt-get update && sudo apt-get install cbm git-lfs ffmpeg git clone https://huggingface.co/spaces/svjack/video-to-sketch && cd video-to-sketch pip install gradio huggingface_hub torch==1.11.0 torchvision==0.12.0 pytorchvideo==0.1.5 pyav==11.4.1 huggingface-cli download \ --repo-type dataset svjack/video-dataset-Lily-Bikini-organized \ --local-dir video-dataset-Lily-Bikini-organized python video_to_sketch_script.py video-dataset-Lily-Bikini-organized video-dataset-Lily-Bikini-sketch-organized --copy_others ''' import gc import os import shutil import argparse import numpy as np import torch from huggingface_hub import hf_hub_download from PIL.Image import Resampling from pytorchvideo.data.encoded_video import EncodedVideo from pytorchvideo.transforms.functional import uniform_temporal_subsample from torchvision.io import write_video from torchvision.transforms.functional import resize from tqdm import tqdm from modeling import Generator MAX_DURATION = 60 OUT_FPS = 18 DEVICE = "cpu" if not torch.cuda.is_available() else "cuda" # Load the model model = Generator(3, 1, 3) weights_path = hf_hub_download("nateraw/image-2-line-drawing", "pytorch_model.bin") model.load_state_dict(torch.load(weights_path, map_location=DEVICE)) model.eval() def process_one_second(vid, start_sec, out_fps): """Process one second of a video at a given fps Args: vid (_type_): A pytorchvideo.EncodedVideo instance containing the video to process start_sec (_type_): The second to start processing at out_fps (_type_): The fps to output the video at Returns: np.array: The processed video as a numpy array with shape (T, H, W, C) """ # C, T, H, W video_arr = vid.get_clip(start_sec, start_sec + 1)["video"] # C, T, H, W where T == frames per second x = uniform_temporal_subsample(video_arr, out_fps) # C, T, H, W where H has been scaled to 256 (This will probably be no bueno on vertical vids but whatever) x = resize(x, 256, Resampling.BICUBIC) # C, T, H, W -> T, C, H, W (basically T acts as batch size now) x = x.permute(1, 0, 2, 3) with torch.no_grad(): # T, 1, H, W out = model(x) # T, C, H, W -> T, H, W, C Rescaled to 0-255 out = out.permute(0, 2, 3, 1).clip(0, 1) * 255 # Greyscale -> RGB out = out.repeat(1, 1, 1, 3) return out def process_video(input_video_path, output_video_path): start_sec = 0 vid = EncodedVideo.from_path(input_video_path) duration = min(MAX_DURATION, int(vid.duration)) for i in tqdm(range(duration), desc="Processing video"): video = process_one_second(vid, start_sec=i + start_sec, out_fps=OUT_FPS) gc.collect() if i == 0: video_all = video else: video_all = np.concatenate((video_all, video)) write_video(output_video_path, video_all, fps=OUT_FPS) def copy_non_video_files(input_path, output_path): """Copy non-video files and directories from input path to output path.""" for item in os.listdir(input_path): item_path = os.path.join(input_path, item) if not item.lower().endswith(('.mp4', '.avi', '.mov', '.mkv')): dest_path = os.path.join(output_path, item) if os.path.isdir(item_path): shutil.copytree(item_path, dest_path) else: shutil.copy2(item_path, dest_path) def main(input_path, output_path, copy_others=False): if not os.path.exists(output_path): os.makedirs(output_path) if copy_others: copy_non_video_files(input_path, output_path) for video_name in os.listdir(input_path): if video_name.lower().endswith(('.mp4', '.avi', '.mov', '.mkv')): input_video_path = os.path.join(input_path, video_name) output_video_path = os.path.join(output_path, video_name) process_video(input_video_path, output_video_path) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Process videos to convert them into sketch videos.") parser.add_argument("input_path", type=str, help="Path to the input directory containing videos.") parser.add_argument("output_path", type=str, help="Path to the output directory for processed videos.") parser.add_argument("--copy_others", action="store_true", help="Copy non-video files and directories from input to output.") args = parser.parse_args() main(args.input_path, args.output_path, args.copy_others)