Created
April 20, 2020 20:02
-
-
Save dfaker/8002cc484054d2be7e07e753f8d37908 to your computer and use it in GitHub Desktop.
Webm Generation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import os | |
import mimetypes | |
import random | |
import mpv | |
import cv2 | |
import numpy as np | |
import subprocess as sp | |
from win32api import GetSystemMetrics | |
import multiprocessing as mp | |
workers=1 | |
maxWidth = 1280 | |
imshape = (100,GetSystemMetrics(0)-10,3) | |
targetSize_max = 4194304 | |
targetSize_min = 4100000 | |
audio_mp = 8 | |
video_mp = 1024 | |
crf=4 | |
default_span=30.0 | |
logo = 'logo.png' | |
threads = 2 | |
files = [] | |
arg = sys.argv[-1] | |
queue = [] | |
breakLoop = False | |
def processWorker(q): | |
while 1: | |
(src,s,e),(cw,ch,cx,cy) = q.get() | |
print( src,s,e,cw,ch,cx,cy) | |
s = max(0,s) | |
dur = abs(s-e) | |
br = int( ((3.8*video_mp)/dur) - ((32 / audio_mp)/dur) ) *8 | |
cropCmd = '' | |
if cx!=0 and cy !=0 and cw!=0 and ch!=0: | |
cropCmd = 'crop={}:{}:{}:{},'.format(cw,ch,cx,cy) | |
brmult=1 | |
os.path.exists('out') or os.mkdir('out') | |
os.path.exists('temp') or os.mkdir('temp') | |
outFilename=os.path.join('out',os.path.splitext(os.path.basename(src))[0]+'.'+str(int(s))+'.'+str(int(e))+".webm") | |
tempname = os.path.join('temp',os.path.splitext(os.path.basename(src))[0]+'.'+str(int(s))+'.'+str(int(e))+".webm") | |
attempt=0 | |
while 1: | |
attempt+=1 | |
print( "Attempt {}: {} br:{}[{}]".format(attempt,os.path.basename(src),br,brmult) ) | |
print('PASS 1 Start.') | |
cmd = ["ffmpeg" | |
,"-y" | |
,"-i" , src | |
,"-i" , logo | |
,"-ss" , str(s) | |
,"-to" , str(e) | |
,"-c:v" ,"libvpx" | |
,"-threads", str(threads) | |
,"-quality", "best" | |
,"-auto-alt-ref", "1" | |
,"-lag-in-frames", "16" | |
,"-slices", "8" | |
,"-passlogfile", tempname+".log" | |
,"-crf" ,str(crf) | |
,"-b:v" ,str(br*brmult)+'K' | |
,"-ac" ,"1" | |
,"-an" | |
,"-filter_complex", "[0:v] "+cropCmd+"scale='min("+str(maxWidth)+"\\,iw):-1' [v0], [v0][1:v] overlay='5:5'" | |
,"-pass" ,"1" | |
,"-f" ,"webm" | |
,"nul"] | |
#print(' '.join(cmd)) | |
proc = sp.Popen(cmd,stderr=sp.PIPE,stdout=sp.PIPE) | |
proc.communicate() | |
print('PASS 2 Start.') | |
cmd = ["ffmpeg" | |
,"-y" | |
,"-i" , src | |
,"-i" , logo | |
,"-ss" , str(s) | |
,"-to" , str(e) | |
,"-c:v" ,"libvpx" | |
,"-threads", str(threads) | |
,"-quality", "best" | |
,"-auto-alt-ref", "1" | |
,"-lag-in-frames", "16" | |
,"-slices", "8" | |
,"-passlogfile", tempname+".log" | |
,"-crf" ,str(crf) | |
,"-b:v" ,str(br*brmult)+'K' | |
,"-ac" ,"1" | |
,"-c:a" ,"libvorbis" | |
,"-b:a" ,"32k" | |
,"-filter_complex", "[0:v] "+cropCmd+"scale='min("+str(maxWidth)+"\\,iw):-1' [v0], [v0][1:v] overlay='5:5'" | |
,"-pass" ,"2" | |
,tempname] | |
#print(' '.join(cmd)) | |
proc = sp.Popen(cmd,stderr=sp.PIPE,stdout=sp.PIPE) | |
proc.communicate() | |
finalSize = os.stat(tempname).st_size | |
if targetSize_min<finalSize<targetSize_max or (finalSize<targetSize_max and attempt>10): | |
print("Complete.") | |
os.rename(tempname,outFilename) | |
break | |
else: | |
if finalSize<targetSize_min: | |
print("File size too small",finalSize,targetSize_min-finalSize) | |
elif finalSize>targetSize_max: | |
print("File size too large",finalSize,finalSize-targetSize_max) | |
brmult= brmult+(1.0-(finalSize/(targetSize_max*0.999) )) | |
q.task_done() | |
if __name__ == '__main__': | |
mp.set_start_method('spawn') | |
q = mp.JoinableQueue() | |
pl = [] | |
for _ in range(workers): | |
p = mp.Process(target=processWorker, args=(q,),daemon=True) | |
p.start() | |
pl.append(p) | |
if os.path.isfile(arg): | |
g = mimetypes.guess_type(arg) | |
if g is not None and g[0] is not None and 'video' in g[0]: | |
files = [arg] | |
elif os.path.isdir(arg): | |
for r,dl,fl in os.walk(arg): | |
for f in fl: | |
p = os.path.join(r,f) | |
print(p) | |
g = mimetypes.guess_type(p) | |
if g is not None and g[0] is not None and 'video' in g[0]: | |
files.append(p) | |
random.shuffle(files) | |
print(files) | |
for src in files: | |
if breakLoop and 'Y' in input('End Seletion?').upper(): | |
break | |
else: | |
breakLoop=False | |
while 1: | |
if breakLoop: | |
break | |
print(src) | |
player = mpv.MPV(input_default_bindings=True, osc=True) | |
player.loop_playlist = 'inf' | |
player.mute = 'yes' | |
player.speed = 2 | |
cw,ch,cx,cy=0,0,0,0 | |
total_duration=None | |
current_time=None | |
selected=False | |
span=default_span | |
keydown=False | |
posa=posb=None | |
xSelectionPos=None | |
player.command('load-script','easycrop.lua') | |
player.play(src) | |
player.autofit=min(1280,maxWidth) | |
player.geometry='100%:50%' | |
seeker = np.zeros(imshape,np.uint8) | |
def updateScrollImage(mouseX): | |
global seeker | |
if mouseX is not None: | |
seeker[:,:,:] = 0 | |
spanDur = (span/total_duration)*imshape[1] | |
seeker[:,max(mouseX-int(spanDur//2),0):min(mouseX+int(spanDur//2),imshape[1]),:]=100 | |
seeker[:,mouseX,:]=255 | |
if total_duration is not None and current_time is not None: | |
seeker[:, min(max(int(imshape[1]*(current_time/total_duration)),0),imshape[1]-1),: ] = (0,255,0) | |
@player.message_handler('easycrop') | |
def luaHandler(w,h,x,y): | |
global cw,ch,cx,cy | |
cw,ch,cx,cy = int(w),int(h),int(x),int(y) | |
print(w,h,x,y) | |
def click(event, x, y, flags, param): | |
global keydown,total_duration,posa,posb,span,xSelectionPos | |
if event in (cv2.EVENT_LBUTTONDOWN,cv2.EVENT_LBUTTONUP): | |
keydown = event==cv2.EVENT_LBUTTONDOWN | |
if event==cv2.EVENT_MOUSEWHEEL: | |
incVal = 0.1 | |
if keydown: | |
incVal = 1.0 | |
if flags>0: | |
span+=incVal | |
else: | |
span-=incVal | |
span = max(1,span) | |
if total_duration is not None: | |
xSelectionPos=x | |
localDur = (x/imshape[1])*total_duration | |
posa = min(localDur-(span/2),0) | |
posb = max(localDur+(span/2),total_duration) | |
player.ab_loop_a=posa | |
player.ab_loop_b=posb | |
player.command('seek', posb-1, 'absolute', 'exact') | |
updateScrollImage(xSelectionPos) | |
if keydown: | |
xSelectionPos=x | |
if total_duration is not None: | |
xSelectionPos=x | |
localDur = (x/imshape[1])*total_duration | |
posa = localDur-(span/2) | |
posb = localDur+(span/2) | |
player.ab_loop_a=posa | |
player.ab_loop_b=posb | |
player.command('seek', posb-1, 'absolute', 'exact') | |
updateScrollImage(xSelectionPos) | |
def setValues(state,key): | |
global player,selected,breakLoop | |
if state=='d-' and key in ('q','e'): | |
player.terminate() | |
selected=True | |
del player | |
if key == 'e': | |
print('EKEY') | |
breakLoop=True | |
player.register_key_binding("q", setValues) | |
player.register_key_binding("e", setValues) | |
player.register_key_binding("CLOSE_WIN", setValues) | |
@player.property_observer('time-pos') | |
def time_observer(_name, value): | |
global total_duration,current_time | |
current_time=value | |
if total_duration is None: | |
total_duration = player.duration | |
updateScrollImage(xSelectionPos) | |
cv2.namedWindow("seeker") | |
cv2.imshow("seeker",seeker) | |
cv2.setMouseCallback("seeker", click) | |
while not selected: | |
cv2.imshow("seeker",seeker) | |
k = cv2.waitKey(1) | |
if k in (ord('q'),ord('e')): | |
setValues('d-',chr(k)) | |
cv2.destroyAllWindows() | |
if not breakLoop: | |
q.put( ((src,posa,posb),(cw,ch,cx,cy)) ) | |
if breakLoop: | |
print('lcBREAK') | |
break | |
q.join() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment