Created
April 20, 2020 20:01
-
-
Save dfaker/d3e9bb9e4bff26c4895f2dc30ee1916d to your computer and use it in GitHub Desktop.
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