|
|
|
|
|
"""Used to compress videos (FPS and dimensions) in the Singularity project.
|
|
|
|
|
|
copied from https://github.com/klauscc/VindLU
|
|
|
"""
|
|
|
import argparse
|
|
|
import os
|
|
|
import shutil
|
|
|
import subprocess
|
|
|
from multiprocessing import Pool
|
|
|
from os.path import exists, join
|
|
|
from pathlib import Path
|
|
|
|
|
|
try:
|
|
|
from psutil import cpu_count
|
|
|
except ImportError:
|
|
|
from multiprocessing import cpu_count
|
|
|
|
|
|
from functools import partial
|
|
|
|
|
|
from PIL import Image
|
|
|
from tqdm import tqdm
|
|
|
|
|
|
|
|
|
def resize_image(input_path, output_path, size=224):
|
|
|
with Image.open(input_path) as img:
|
|
|
w, h = img.width, img.height
|
|
|
r = 1. * w / h
|
|
|
if w > h:
|
|
|
h = size
|
|
|
w = r * size
|
|
|
else:
|
|
|
h = size / r
|
|
|
w = size
|
|
|
|
|
|
img_resized = img.resize((int(w), int(h)))
|
|
|
img_resized.save(output_path)
|
|
|
|
|
|
|
|
|
def _compress_images(input_output_pair, size=224):
|
|
|
"""Scale and downsample an input image to a given fps and size (shorter
|
|
|
side size).
|
|
|
|
|
|
This also removes the audio from the image.
|
|
|
"""
|
|
|
input_image_path, output_image_path = input_output_pair
|
|
|
try:
|
|
|
resize_image(input_image_path, output_image_path, size)
|
|
|
except Exception as e:
|
|
|
print(f'Caught Exception {e}')
|
|
|
|
|
|
|
|
|
def _compress_videos(input_output_pair, size=224, fps=3):
|
|
|
"""Scale and downsample an input video to a given fps and size (shorter
|
|
|
side size).
|
|
|
|
|
|
This also removes the audio from the video.
|
|
|
"""
|
|
|
input_file_path, output_file_path = input_output_pair
|
|
|
try:
|
|
|
command = [
|
|
|
'ffmpeg',
|
|
|
'-y',
|
|
|
'-i',
|
|
|
input_file_path,
|
|
|
'-filter:v',
|
|
|
f"scale='if(gt(a,1),trunc(oh*a/2)*2,{size})':'if(gt(a,1),{size},trunc(ow*a/2)*2)'",
|
|
|
'-map',
|
|
|
'0:v',
|
|
|
'-r',
|
|
|
str(fps),
|
|
|
|
|
|
output_file_path,
|
|
|
]
|
|
|
subprocess.run(
|
|
|
command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
except Exception as e:
|
|
|
raise e
|
|
|
|
|
|
|
|
|
def _compress(input_output_pair, fps=3, size=224, file_type='image'):
|
|
|
if file_type == 'image':
|
|
|
_compress_images(input_output_pair, size)
|
|
|
elif file_type == 'video':
|
|
|
_compress_videos(input_output_pair, size, fps)
|
|
|
|
|
|
|
|
|
def prepare_input_output_pairs(input_root,
|
|
|
output_root,
|
|
|
input_file_list_path=None):
|
|
|
|
|
|
if input_file_list_path:
|
|
|
with open(input_file_list_path, 'r') as f:
|
|
|
filenames = [s.strip() for s in f.readlines()]
|
|
|
else:
|
|
|
filenames = [
|
|
|
video_path.name for video_path in Path(input_root).glob('*.mp4')
|
|
|
]
|
|
|
print(f'There are {len(filenames)} video/images files loaded from list.')
|
|
|
input_file_path_list = []
|
|
|
output_file_path_list = []
|
|
|
for e in tqdm(filenames, desc='find un-processed videos/images'):
|
|
|
input_file_path = join(input_root, e)
|
|
|
output_file_path = join(output_root, e)
|
|
|
if not exists(output_file_path):
|
|
|
input_file_path_list.append(input_file_path)
|
|
|
output_file_path_list.append(output_file_path)
|
|
|
return input_file_path_list, output_file_path_list
|
|
|
|
|
|
|
|
|
def run_compress():
|
|
|
parser = argparse.ArgumentParser(
|
|
|
description='Compress videos/images for speed-up')
|
|
|
parser.add_argument(
|
|
|
'--input_root', type=str, help='input root', required=True)
|
|
|
parser.add_argument(
|
|
|
'--input_file_list_path',
|
|
|
type=str,
|
|
|
default=None,
|
|
|
help='list of video filenames under args.input_root, it can be '
|
|
|
'created efficiently with `ls -U /path/to/video >> /path/to/video_filenames.txt`'
|
|
|
)
|
|
|
parser.add_argument(
|
|
|
'--output_root', type=str, help='output root', required=True)
|
|
|
parser.add_argument(
|
|
|
'--size',
|
|
|
type=int,
|
|
|
default=224,
|
|
|
help='shorter side size, aspect ratio is kept')
|
|
|
parser.add_argument('--num_workers', type=int, default=24, help='#workers')
|
|
|
parser.add_argument(
|
|
|
'--fps',
|
|
|
type=int,
|
|
|
default=3,
|
|
|
help='fps for output video, ignored if file_type == image')
|
|
|
parser.add_argument(
|
|
|
'--file_type',
|
|
|
type=str,
|
|
|
choices=['image', 'video'],
|
|
|
help='input file type')
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
|
input_root = args.input_root
|
|
|
output_root = args.output_root
|
|
|
assert input_root != output_root
|
|
|
if not exists(output_root):
|
|
|
os.makedirs(output_root, exist_ok=True)
|
|
|
|
|
|
|
|
|
input_file_path_list, output_file_path_list = prepare_input_output_pairs(
|
|
|
input_root,
|
|
|
output_root,
|
|
|
input_file_list_path=args.input_file_list_path,
|
|
|
)
|
|
|
print(f'input_file_path_list[:3] {input_file_path_list[:3]}')
|
|
|
print(f'output_file_path_list[:3] {output_file_path_list[:3]}')
|
|
|
print('Total videos/images need to process: {}'.format(
|
|
|
len(input_file_path_list)))
|
|
|
|
|
|
|
|
|
num_cores = cpu_count()
|
|
|
num_workers = args.num_workers
|
|
|
print(
|
|
|
f'Begin with {num_cores}-core logical processor, {num_workers} workers'
|
|
|
)
|
|
|
compress = partial(
|
|
|
_compress, fps=args.fps, size=args.size, file_type=args.file_type)
|
|
|
input_pairs = list(zip(input_file_path_list, output_file_path_list))
|
|
|
with Pool(num_workers) as pool, tqdm(
|
|
|
total=len(input_file_path_list),
|
|
|
desc='re-encoding videos/images') as pbar:
|
|
|
for idx, _ in enumerate(
|
|
|
pool.imap_unordered(compress, input_pairs, chunksize=32)):
|
|
|
pbar.update(1)
|
|
|
|
|
|
|
|
|
print('Compress finished, copy-paste failed files...')
|
|
|
copy_count = 0
|
|
|
for input_file_path, output_file_path in zip(input_file_path_list,
|
|
|
output_file_path_list):
|
|
|
if exists(input_file_path):
|
|
|
if exists(output_file_path) is False or os.path.getsize(
|
|
|
output_file_path) < 1.:
|
|
|
copy_count += 1
|
|
|
shutil.copyfile(input_file_path, output_file_path)
|
|
|
print('Copy and replace file: {}'.format(output_file_path))
|
|
|
print(f'copy_count {copy_count}')
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
run_compress()
|
|
|
|