2020-01-08 00:39:49 +00:00
|
|
|
#!/usr/bin/python3
|
|
|
|
"""
|
|
|
|
mkvmerge required (python-pymkv)
|
2020-01-08 01:36:20 +00:00
|
|
|
ffmpeg required
|
|
|
|
TODO:
|
2020-01-08 22:43:20 +00:00
|
|
|
DONE make encoding queue with limiting by workers
|
|
|
|
DONE make concatenating videos after encoding
|
2020-01-09 14:54:01 +00:00
|
|
|
DONE make passing your arguments for encoding,
|
2020-01-11 06:02:22 +00:00
|
|
|
2-pass encode by default for better quality
|
2020-01-08 01:52:57 +00:00
|
|
|
make separate audio and encode it separately,
|
2020-01-08 00:39:49 +00:00
|
|
|
"""
|
2020-01-08 00:20:18 +00:00
|
|
|
import os
|
2020-01-09 19:27:27 +00:00
|
|
|
from os.path import join
|
2020-01-09 19:35:43 +00:00
|
|
|
from psutil import virtual_memory
|
2020-01-11 06:10:21 +00:00
|
|
|
from subprocess import Popen, call
|
2020-01-09 14:54:01 +00:00
|
|
|
import argparse
|
2020-01-09 15:50:26 +00:00
|
|
|
import time
|
2020-01-11 01:18:01 +00:00
|
|
|
from shutil import rmtree
|
2020-01-09 19:09:49 +00:00
|
|
|
from math import ceil
|
2020-01-08 20:44:44 +00:00
|
|
|
from multiprocessing import Pool
|
2020-01-08 18:56:47 +00:00
|
|
|
try:
|
|
|
|
import scenedetect
|
2020-01-11 07:26:20 +00:00
|
|
|
except ImportError:
|
2020-01-08 18:56:47 +00:00
|
|
|
print('ERROR: No PyScenedetect installed, try: sudo pip install scenedetect')
|
2020-01-08 00:20:18 +00:00
|
|
|
|
|
|
|
|
2020-01-11 06:06:50 +00:00
|
|
|
DEFAULT_ENCODE = ' -h 40 -w 70 --passes=1 --tile-columns=2 --tile-rows=2 --cpu-used=8 --end-usage=q --cq-level=63 --aq-mode=0'
|
|
|
|
DEFAULT_AUDIO = '-c:a libopus -ac 1 -b:a 12k'
|
2020-01-11 07:27:13 +00:00
|
|
|
#-w 252 -h 144
|
|
|
|
DEFAULT_ENCODE = ' -w 70 -h 40 --passes=1 --cpu-used=8 --end-usage=q --cq-level=63 --aq-mode=0'
|
|
|
|
DEFAULT_AUDIO = '-c:a libopus -ac 1 -b:a 24k'
|
2020-01-10 23:19:27 +00:00
|
|
|
FFMPEG = 'ffmpeg -hide_banner -loglevel warning '
|
|
|
|
|
|
|
|
|
2020-01-11 01:19:27 +00:00
|
|
|
class ProgressBar:
|
|
|
|
"""
|
|
|
|
Progress Bar for tracking encoding progress
|
|
|
|
"""
|
|
|
|
|
2020-01-11 07:27:58 +00:00
|
|
|
def __init__(self, size):
|
2020-01-11 02:48:58 +00:00
|
|
|
self.iteration: int = 0
|
2020-01-11 07:27:58 +00:00
|
|
|
self.total = size
|
2020-01-11 02:48:58 +00:00
|
|
|
self.length = 50
|
2020-01-11 07:27:58 +00:00
|
|
|
self.fill = '█'
|
|
|
|
|
|
|
|
# Print on empty bar on initialization
|
|
|
|
self.print()
|
2020-01-11 01:19:27 +00:00
|
|
|
|
|
|
|
def print(self):
|
2020-01-11 02:48:58 +00:00
|
|
|
terminal_size = int(os.popen('stty size', 'r').read().split()[1])
|
2020-01-11 06:06:37 +00:00
|
|
|
self.length = terminal_size - 14 - 2 * len(str(self.total))
|
2020-01-11 02:48:58 +00:00
|
|
|
|
2020-01-11 06:40:20 +00:00
|
|
|
if self.iteration == 0:
|
2020-01-11 01:19:27 +00:00
|
|
|
percent = 0
|
|
|
|
filled_length = 0
|
|
|
|
else:
|
2020-01-11 07:27:58 +00:00
|
|
|
percent = round(100 * (self.iteration / self.total), 1)
|
2020-01-11 01:19:27 +00:00
|
|
|
filled_length = int(self.length * self.iteration // self.total)
|
|
|
|
|
|
|
|
bar_size = (self.fill * filled_length) + '-' * (self.length - filled_length)
|
2020-01-11 07:27:58 +00:00
|
|
|
print(f'\r|{bar_size}| {percent}% {self.iteration}/{self.total} ', end='')
|
2020-01-11 02:48:58 +00:00
|
|
|
|
2020-01-11 01:19:27 +00:00
|
|
|
|
|
|
|
def tick(self):
|
2020-01-11 06:40:20 +00:00
|
|
|
self.iteration += 1
|
2020-01-11 01:19:27 +00:00
|
|
|
self.print()
|
|
|
|
|
|
|
|
|
2020-01-09 15:49:28 +00:00
|
|
|
def arg_parsing():
|
|
|
|
"""
|
|
|
|
Command line parser
|
2020-01-09 20:15:29 +00:00
|
|
|
Have default params
|
2020-01-09 15:49:28 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser()
|
2020-01-10 23:19:27 +00:00
|
|
|
parser.add_argument('--encoding_params', type=str, default=DEFAULT_ENCODE, help='AOMENC settings')
|
2020-01-09 15:49:28 +00:00
|
|
|
parser.add_argument('--input_file', '-i', type=str, default='bruh.mp4', help='input video file')
|
2020-01-09 20:15:29 +00:00
|
|
|
parser.add_argument('--num_worker', '-t', type=int, default=determine_resources(), help='number of encodes running at a time')
|
2020-01-11 03:54:06 +00:00
|
|
|
parser.add_argument('--audio_params', '-a' , type=str, default=DEFAULT_AUDIO, help='ffmpeg audio encode settings')
|
2020-01-09 15:49:28 +00:00
|
|
|
return parser.parse_args()
|
|
|
|
|
|
|
|
|
2020-01-09 19:09:49 +00:00
|
|
|
def determine_resources():
|
2020-01-11 01:19:27 +00:00
|
|
|
"""
|
|
|
|
Returns number of workers that machine can handle
|
|
|
|
:return: int
|
|
|
|
"""
|
2020-01-09 19:09:49 +00:00
|
|
|
cpu = os.cpu_count()
|
2020-01-09 19:35:43 +00:00
|
|
|
ram = round(virtual_memory().total / 2**30)
|
2020-01-11 06:44:24 +00:00
|
|
|
return ceil(min(cpu, ram/1.5))
|
2020-01-08 00:20:18 +00:00
|
|
|
|
|
|
|
|
2020-01-11 04:16:31 +00:00
|
|
|
def setup(input_file):
|
|
|
|
|
|
|
|
if not os.path.exists(input_file):
|
|
|
|
print("File don't exist")
|
|
|
|
exit()
|
|
|
|
|
|
|
|
# Make temporal directories, and remove them if already presented
|
|
|
|
if os.path.isdir(join(os.getcwd(), "temp")):
|
|
|
|
rmtree(join(os.getcwd(), "temp"))
|
|
|
|
|
|
|
|
os.makedirs(join(os.getcwd(), 'temp', 'split'))
|
|
|
|
os.makedirs(join(os.getcwd(), 'temp', 'encode'))
|
|
|
|
|
|
|
|
|
2020-01-11 03:54:06 +00:00
|
|
|
def extract_audio(input_vid, audio_params):
|
2020-01-09 15:50:26 +00:00
|
|
|
"""
|
|
|
|
Extracting audio from video file
|
2020-01-11 07:27:13 +00:00
|
|
|
Encoding audio if needed
|
2020-01-09 15:50:26 +00:00
|
|
|
"""
|
2020-01-11 03:54:06 +00:00
|
|
|
cmd = f'{FFMPEG} -i {join(os.getcwd(),input_vid)} -vn {audio_params} {join(os.getcwd(),"temp","audio.mkv")}'
|
2020-01-10 23:18:36 +00:00
|
|
|
Popen(cmd, shell=True).wait()
|
2020-01-08 22:43:20 +00:00
|
|
|
|
|
|
|
|
2020-01-08 00:39:49 +00:00
|
|
|
def split_video(input_vid):
|
2020-01-11 07:02:53 +00:00
|
|
|
"""
|
|
|
|
PySceneDetect used split video by scenes and pass it to encoder
|
2020-01-11 07:27:13 +00:00
|
|
|
Optimal threshold settings 15-50
|
2020-01-11 07:02:53 +00:00
|
|
|
"""
|
2020-01-10 23:18:36 +00:00
|
|
|
cmd2 = f'scenedetect -q -i {input_vid} --output temp/split detect-content --threshold 50 split-video -c'
|
|
|
|
call(cmd2, shell=True)
|
2020-01-09 15:52:11 +00:00
|
|
|
print(f'Video {input_vid} splitted')
|
2020-01-08 00:20:18 +00:00
|
|
|
|
2020-01-08 18:56:47 +00:00
|
|
|
|
|
|
|
def get_video_queue(source_path):
|
|
|
|
videos = []
|
|
|
|
for root, dirs, files in os.walk(source_path):
|
|
|
|
for file in files:
|
|
|
|
f = os.path.getsize(os.path.join(root, file))
|
|
|
|
videos.append([file, f])
|
|
|
|
|
|
|
|
videos = sorted(videos, key=lambda x: -x[1])
|
2020-01-10 23:19:27 +00:00
|
|
|
print(f'Splited videos: {len(videos)}')
|
2020-01-08 18:56:47 +00:00
|
|
|
return videos
|
2020-01-08 20:44:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
def encode(commands):
|
2020-01-09 15:33:47 +00:00
|
|
|
"""
|
|
|
|
Passing encoding params to ffmpeg for encoding
|
|
|
|
TODO:
|
|
|
|
Replace ffmpeg with aomenc because ffmpeg libaom doen't work with parameters properly
|
|
|
|
"""
|
2020-01-10 23:19:27 +00:00
|
|
|
|
|
|
|
cmd = f'{FFMPEG} {commands[0]}'
|
2020-01-11 01:19:27 +00:00
|
|
|
|
2020-01-11 06:10:21 +00:00
|
|
|
Popen(cmd, shell=True).wait()
|
|
|
|
|
2020-01-08 22:43:20 +00:00
|
|
|
|
2020-01-10 03:08:43 +00:00
|
|
|
def concat(input_video):
|
2020-01-08 22:43:20 +00:00
|
|
|
"""
|
|
|
|
Using FFMPEG to concatenate all encoded videos to 1 file.
|
|
|
|
Reading all files in A-Z order and saving it to concat.txt
|
|
|
|
"""
|
|
|
|
with open(f'{os.getcwd()}/temp/concat.txt', 'w') as f:
|
2020-01-08 20:44:44 +00:00
|
|
|
|
2020-01-09 19:27:27 +00:00
|
|
|
for root, firs, files in os.walk(join(os.getcwd(), 'temp', 'encode')):
|
2020-01-08 22:43:20 +00:00
|
|
|
for file in sorted(files):
|
2020-01-09 19:27:27 +00:00
|
|
|
f.write(f"file '{join(root, file)}'\n")
|
2020-01-08 20:44:44 +00:00
|
|
|
|
2020-01-11 01:18:33 +00:00
|
|
|
cmd = f'{FFMPEG} -f concat -safe 0 -i {join(os.getcwd(), "temp", "concat.txt")} -i {join(os.getcwd(), "temp", "audio.mkv")} -c copy -y {input_video.split(".")[0]}_av1.webm'
|
2020-01-11 06:10:21 +00:00
|
|
|
Popen(cmd, shell=True).wait()
|
2020-01-08 21:27:21 +00:00
|
|
|
|
|
|
|
|
2020-01-11 04:16:31 +00:00
|
|
|
def main(arg):
|
2020-01-08 21:27:21 +00:00
|
|
|
|
2020-01-11 04:16:31 +00:00
|
|
|
# Check validity of request and create temp folders/files
|
|
|
|
setup(arg.input_file)
|
2020-01-08 21:27:21 +00:00
|
|
|
|
2020-01-09 16:24:37 +00:00
|
|
|
# Extracting audio
|
2020-01-11 04:16:31 +00:00
|
|
|
extract_audio(arg.input_file, arg.audio_params)
|
2020-01-09 16:24:37 +00:00
|
|
|
|
2020-01-11 02:48:58 +00:00
|
|
|
# Splitting video and sorting big-first
|
2020-01-11 04:16:31 +00:00
|
|
|
split_video(arg.input_file)
|
2020-01-10 23:19:27 +00:00
|
|
|
vid_queue = get_video_queue('temp/split')
|
2020-01-08 20:44:44 +00:00
|
|
|
files = [i[0] for i in vid_queue[:-1]]
|
2020-01-08 21:27:21 +00:00
|
|
|
|
|
|
|
# Making list of commands for encoding
|
2020-01-10 23:48:53 +00:00
|
|
|
commands = [(f'-i {join(os.getcwd(), "temp", "split", file)} -pix_fmt yuv420p -f yuv4mpegpipe - |' +
|
2020-01-11 04:16:31 +00:00
|
|
|
f' aomenc -q {arg.encoding_params} -o {join(os.getcwd(), "temp", "encode", file)} -', file) for file in files]
|
2020-01-08 21:27:21 +00:00
|
|
|
|
|
|
|
# Creating threading pool to encode fixed amount of files at the same time
|
2020-01-11 04:16:31 +00:00
|
|
|
print(f'Starting encoding with {arg.num_worker} workers. \nParameters:{arg.encoding_params}\nEncoding..')
|
2020-01-11 01:19:27 +00:00
|
|
|
|
|
|
|
# Progress Bar
|
2020-01-11 07:27:58 +00:00
|
|
|
bar = ProgressBar(len(vid_queue))
|
2020-01-11 01:19:27 +00:00
|
|
|
|
2020-01-11 06:10:21 +00:00
|
|
|
# async_encode(commands, num_worker)
|
2020-01-11 04:16:31 +00:00
|
|
|
pool = Pool(arg.num_worker)
|
2020-01-11 06:40:20 +00:00
|
|
|
for i, _ in enumerate(pool.imap_unordered(encode, commands), 1):
|
|
|
|
bar.tick()
|
|
|
|
|
|
|
|
bar.tick()
|
2020-01-08 20:44:44 +00:00
|
|
|
|
2020-01-08 22:43:20 +00:00
|
|
|
# Merging all encoded videos to 1
|
2020-01-11 04:16:31 +00:00
|
|
|
concat(arg.input_file)
|
2020-01-08 20:44:44 +00:00
|
|
|
|
2020-01-08 23:36:39 +00:00
|
|
|
|
2020-01-08 20:44:44 +00:00
|
|
|
if __name__ == '__main__':
|
2020-01-09 15:33:47 +00:00
|
|
|
|
|
|
|
# Main thread
|
2020-01-09 15:49:28 +00:00
|
|
|
start = time.time()
|
2020-01-11 06:10:21 +00:00
|
|
|
|
2020-01-11 04:16:31 +00:00
|
|
|
main(arg_parsing())
|
2020-01-11 06:10:21 +00:00
|
|
|
|
2020-01-11 06:06:37 +00:00
|
|
|
print(f'\nCompleted in {round(time.time()-start, 1)} seconds\n')
|
2020-01-09 16:25:27 +00:00
|
|
|
|
|
|
|
# Delete temp folders
|
2020-01-11 01:19:01 +00:00
|
|
|
rmtree(join(os.getcwd(), "temp"))
|
2020-01-11 07:26:47 +00:00
|
|
|
|
|
|
|
# To prevent console from hanging
|
2020-01-11 06:40:20 +00:00
|
|
|
os.popen('stty sane', 'r')
|