Skip to content

Instantly share code, notes, and snippets.

@505e06b2
Created January 9, 2020 15:19
Show Gist options
  • Save 505e06b2/b18eb00de7ab940aa15dcab3943a6a1f to your computer and use it in GitHub Desktop.
Save 505e06b2/b18eb00de7ab940aa15dcab3943a6a1f to your computer and use it in GitHub Desktop.
FFMPEG Webm Encoder - With Progress
#!/usr/bin/env python3
import subprocess, sys, multiprocessing, json, os, shlex, re
from datetime import timedelta
error_messages = {
"param_len": """Not enough parameters
Format: %s [input_file] (optional)[parameters] [movie_title]
"""
}
output_display = "-hide_banner -loglevel error"
default_params = " ".join([
"-c:v libvpx -vf %sscale=w=1280:h=720:force_original_aspect_ratio=decrease -b:v %s -crf 10",
'-c:a libopus -ac 2',#-af "pan=stereo|FL=0.5*FC+0.707*FL+0.707*BL+0.5*LFE|FR=0.5*FC+0.707*FR+0.707*BR+0.5*LFE"',
"-map_metadata -1 -metadata title=\"%s\" -threads %d -y -progress -"
])
def parseParams(params):
parsed = shlex.split(params)
vf_param = ""
bitrate = "1M"
try:
param_contents = parsed.index("-vf")+1
vf_param = parsed[param_contents] + ","
except ValueError:
pass
try:
param_contents = parsed.index("-b:v")+1
bitrate = parsed[param_contents]
except ValueError:
pass
return vf_param, bitrate
def videoFilesize(filename):
if not os.path.isfile(filename):
return 0
return os.path.getsize(filename)/1024 #to kb
def videoLength(filename):
if not os.path.isfile(filename):
return 0.0
result = subprocess.run(
shlex.split('ffprobe %s -show_entries format=duration -print_format json "%s"' % (
output_display,
filename
)),
stdout = subprocess.PIPE
)
return int(json.loads(result.stdout.decode("utf-8"))["format"]["duration"].replace(".", ""))
def encode(file_in, movie_title, params = ""):
vf_filters, v_bitrate = parseParams(params)
p = subprocess.Popen(
shlex.split('ffmpeg %s -i "%s" %s %s "%s.webm"' % (
output_display,
file_in,
params, #get overwritten by defaults
default_params % (vf_filters, v_bitrate, movie_title, multiprocessing.cpu_count()),
movie_title.lower().replace(" ", "_")
)),
stdout = subprocess.PIPE
)
ffmpeg_output = {}
video_length = videoLength(file_in)
video_filesize = videoFilesize(file_in)
if video_length <= 0 or video_filesize <= 0:
print("Input file not found!")
return
print("Encoding...\nPress [q] to exit")
for line in iter(p.stdout.readline, b""):
l = line.decode("utf-8").rstrip().split("=")
if l[0] != "progress":
if l[0] == "out_time_ms" or l[0] == "total_size": #if not structured like this, the next if will fail
ffmpeg_output[l[0]] = int(l[1])
elif l[0] == "speed" and l[1] != "N/A":
ffmpeg_output[l[0]] = float(l[1][:-1])
elif ffmpeg_output["out_time_ms"] > 0:
percent = ffmpeg_output["out_time_ms"] / float(video_length)
filesize = (ffmpeg_output["total_size"] / percent) / 1024 #to kb
print("\rEst. Remaining Time: %s | Est. Filesize: %s | %6.2f%% " % ( #format example: " 2.44%"
':'.join(str(timedelta(microseconds=( #only want the (days), hours, mins
int((video_length - ffmpeg_output["out_time_ms"]) / ffmpeg_output["speed"])
))).split(":")[:2]),
("%7dK" if filesize < video_filesize else "!!%7dK!!") % (filesize), #alert if larger than original video
percent * 100
), end = "")
p.wait() #ensure stdout isn't stolen
print() #just have a newline
if __name__ == "__main__":
if len(sys.argv) < 3:
sys.stderr.write( error_messages["param_len"] % (sys.argv[0]) )
sys.exit(1)
sys.argv.pop(0)
i = sys.argv.pop(0)
o = sys.argv.pop(-1)
encode(i, o, " ".join(sys.argv))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment