--[[ Script for encoding / hardsubbing Options: - encode whole video / a clip - hardsub 1 or 2 subtitle files or only encode - use vsfilter or vsfiltermod for each subtitle track - encode to mp4 or mkv - mux with audio Requirements: - x264.exe - vsfilter.dll / vsfiltermod.dll for hardsubbing - avisynth (not required when encoding for mocha) 'Encode clip for mocha' does automatically the following (meaning you don't have to do those manually): - disables 10bit - enables trimming - disables subtitles - sets target to .mp4 - disables avisynth use --]] script_name="Encode - Hardsub" script_description="Encode a clip with or without hardsubs" script_author="unanimated" script_version="1.2" script_namespace="ua.EncodeHardsub" local haveDepCtrl,DependencyControl,depRec=pcall(require,"l0.DependencyControl") if haveDepCtrl then script_version="1.2.0" depRec=DependencyControl{feed="https://raw.githubusercontent.com/TypesettingTools/unanimated-Aegisub-Scripts/master/DependencyControl.json"} end function encode(subs,sel) ADD=aegisub.dialog.display ADP=aegisub.decode_path ADO=aegisub.dialog.open ak=aegisub.cancel enconfig=ADP("?user").."\\encode_hardsub.conf" defsett="--crf 18 --ref 10 --bframes 10 --merange 32 --me umh --subme 10 --trellis 2 --direct auto --b-adapt 2 --partitions all" defmsett="--profile baseline --level 1.0 --crf 16 --fps 24000/1001" scriptpath=ADP("?script").."\\" scriptname=aegisub.file_name() vpath=ADP("?video").."\\" ms2fr=aegisub.frame_from_ms fr2ms=aegisub.ms_from_frame sframe=999999 eframe=0 videoname=nil file=io.open(enconfig) if file~=nil then konf=file:read("*all") io.close(file) xpath=konf:match("xpath:(.-)\n") xpath10=konf:match("xpath10:(.-)\n") sett=konf:match("settings:(.-)\n") vsfpath=konf:match("vsfpath:(.-)\n") vsfmpath=konf:match("vsfmpath:(.-)\n") mmgpath=konf:match("mmgpath:(.-)\n") or "" vtype=konf:match("vtype:(.-)\n") vsf1=konf:match("filter1:(.-)\n") vsf2=konf:match("filter2:(.-)\n") targ=konf:match("targ:(.-)\n") target=konf:match("target:(.-)\n") msett=konf:match("mocha:(.-)\n") settlist=konf:match("(settings1:.*\n)$") or "" else xpath="" xpath10="" vsfpath="" vsfmpath="" mmgpath="" vtype=".mkv" vsf1="vsfilter" vsf2="vsfilter" sett=defsett msett=defmsett settlist="" targ="Same as source" target="" end for i=1,#subs do if subs[i].class=="info" then if subs[i].key=="Video File" then videoname=subs[i].value break end end if subs[i].class~="info" then break end end if videoname==nil then videoname=aegisub.project_properties().video_file:gsub("^.*\\","") end if videoname==nil or videoname=="" or aegisub.frame_from_ms(10)==nil then t_error("No video detected.",1) end vid2=videoname:gsub("%.[^%.]+","") :gsub("_?premux","") :gsub("_?workraw","") vid2=vid2.."_hardsub" for z,i in ipairs(sel) do line=subs[i] start=line.start_time endt=line.end_time sfr=ms2fr(start) efr=ms2fr(endt) if sfreframe then eframe=efr end end GUI={ {x=0,y=0,class="label",label="x264.exe 8-bit:"}, {x=1,y=0,width=6,class="edit",name="xpath",value=xpath or ""}, {x=7,y=0,class="label",label=" x264 10-bit:"}, {x=8,y=0,width=2,class="edit",name="xpath10",value=xpath10 or ""}, {x=0,y=1,class="label",label="vsfilter.dll:"}, {x=1,y=1,width=6,class="edit",name="vsf",value=vsfpath or ""}, {x=7,y=1,class="label",label=" vsfiltermod.dll:"}, {x=8,y=1,width=2,class="edit",name="vsfm",value=vsfmpath or "",hint="only needed if you're using it"}, {x=0,y=2,class="label",label="Source video:"}, {x=1,y=2,width=6,class="edit",name="vid",value=videoname}, {x=7,y=2,class="label",label=" mkvmerge.exe:"}, {x=8,y=2,width=2,class="edit",name="mmg",value=mmgpath or "",hint="only needed if you're muxing audio"}, {x=0,y=3,class="label",label="Target folder:"}, {x=1,y=3,width=2,class="dropdown",name="targ",value=targ,items={"Same as source","Custom:"}}, {x=3,y=3,width=7,class="edit",name="target",value=target}, {x=0,y=4,class="label",label="Encode name:"}, {x=1,y=4,width=2,class="dropdown",name="vtype",value=vtype,items={".mkv",".mp4"}}, {x=3,y=4,width=7,class="edit",name="vid2",value=vid2}, {x=0,y=5,class="label",label="Primary subs:"}, {x=1,y=5,width=2,class="dropdown",name="filter1",value=vsf1,items={"none","vsfilter","vsfiltermod"}}, {x=3,y=5,width=7,class="edit",name="first",value=scriptpath..scriptname}, {x=0,y=6,class="checkbox",name="sec",label="Secondary:"}, {x=1,y=6,width=2,class="dropdown",name="filter2",value=vsf2,items={"vsfilter","vsfiltermod"}}, {x=3,y=6,width=7,class="edit",name="second",value=secondary or ""}, {x=0,y=7,class="label",label="Encoder settings:"}, {x=1,y=7,width=9,class="edit",name="encset",value=sett}, {x=0,y=8,class="label",label="Settings 4 mocha:"}, {x=1,y=8,width=9,class="edit",name="encmocha",value=msett or defmsett}, {x=0,y=9,class="checkbox",name="trim",label="Trim from:",hint="Encodes only current selection"}, {x=1,y=9,width=3,class="intedit",name="sf",value=sframe}, {x=4,y=9,class="label",label="to: "}, {x=5,y=9,width=2,class="intedit",name="ef",value=eframe}, {x=7,y=9,width=2,class="label",label=" If checked, frames are added to Encode name "}, {x=9,y=9,width=1,class="checkbox",name="audio",label="Mux with audio",hint="whether you're trimming or not"}, {x=0,y=10,width=2,class="checkbox",name="mocha",label="Encode clip for mocha "}, {x=2,y=10,width=1,class="checkbox",name="tenbit",label="10 bit"}, {x=5,y=10,width=1,class="checkbox",name="delbat",label="Delete batch file",value=true}, {x=6,y=10,width=2,class="checkbox",name="delavs",label="Delete avisynth script",value=true}, {x=8,y=10,width=1,class="checkbox",name="delAV",label="Delete A/V after muxing",value=true,hint="Delete audio/video files only needed for muxing"}, {x=9,y=10,width=1,class="checkbox",name="pause",label="Keep cmd window open "}, } repeat if P=="Default enc. settings" then gui("encset",defsett) end if P=="x264" then x264_path=ADO("x264","",scriptpath,"*.exe",false,true) gui("xpath",x264_path) end if P=="x264 10bit" then x26410_path=ADO("x264 10-bit","",scriptpath,"*.exe",false,true) gui("xpath10",x26410_path) end if P=="vsfilter" then vsf_path=ADO("vsfilter","",scriptpath,"*.dll",false,true) gui("vsf",vsf_path) end if P=="vsfiltermod" then vsfm_path=ADO("vsfiltermod","",scriptpath,"*.dll",false,true) gui("vsfm",vsfm_path) end if P=="Target" then tgt_path=ADO("Target folder for encodes (Select any file in it)",".",scriptpath,"",false,false) if tgt_path then tgt_path=tgt_path:gsub("(.*\\).-$","%1") end gui("target",tgt_path) end if P=="Secondary" then sec_path=ADO("Secondary subs","",scriptpath,"*.ass",false,true) gui("second",sec_path) end if P=="Enc. set." then enclist={defsett} for set in settlist:gmatch("settings%d:(.-)\n") do table.insert(enclist,set) end encodings={{class="dropdown",name="enko",items=enclist,value=defsett}} press,rez=ADD(encodings,{"OK","Cancel"},{ok='OK',close='Cancel'}) for k,v in ipairs(GUI) do if v.name=="encset" then v.value=rez.enko else v.value=res[v.name] end end end if P=="Save" then konf="xpath:"..res.xpath.."\nxpath10:"..res.xpath10.."\nvsfpath:"..res.vsf.."\nvsfmpath:"..res.vsfm.."\nmmgpath:"..res.mmg.."\nvtype:"..res.vtype.."\nfilter1:"..res.filter1.."\nfilter2:"..res.filter2.."\ntarg:"..res.targ.."\ntarget:"..res.target.."\nmocha:"..res.encmocha.."\nsettings:"..res.encset.."\n" if res.encset~=sett then settlist=settlist:gsub("settings9:.-\n","") set1=esc(sett) if not settlist:match(set1) then for i=8,1,-1 do settlist=settlist:gsub("(settings)"..i,"%1"..i+1) end settlist="settings1:"..sett.."\n"..settlist end end konf=konf..settlist file=io.open(enconfig,"w") file:write(konf) file:close() for k,v in ipairs(GUI) do v.value=res[v.name] end ADD({{class="label",label="Settings saved to:\n"..enconfig}},{"OK"},{close='OK'}) end P,res=ADD(GUI, {"Encode","x264","x264 10bit","vsfilter","vsfiltermod","Target","Secondary","Enc. set.","Save","Cancel"},{ok='Encode',close='Cancel'}) until P=="Encode" or P=="Cancel" if P=="Cancel" then ak() end ---------------------------------------------------------------------------------------------------------------------------------------- videoname=res.vid encname=res.vid2 mkvmerge=res.mmg target=vpath vfull=vpath..videoname vsm=0 if res.targ=="Custom:" then target=res.target end -- mocha if res.mocha then res.vtype=".mp4" res.tenbit=false res.trim=true res.encset=res.encmocha.." --seek "..res.sf.." --frames "..res.ef-res.sf res.delavs=false encname=encname:gsub("_hardsub","") source=quo(vfull) end if res.filter1=="none" then res.sec=false encname=encname:gsub("_hardsub","_encode") end if res.trim then encname=encname.."_"..res.sf.."-"..res.ef encname=encname:gsub("_encode","") end if res.tenbit then xpath=res.xpath10 else xpath=res.xpath end file=io.open(xpath) if file==nil then t_error(xpath.."\nERROR: File does not exist (x264).",true) else file:close() end file=io.open(vfull) if file==nil then t_error(vfull.."\nERROR: File does not exist (video source).",true) else file:close() end -- avisynth if res.mocha==false then if res.filter1~="none" and res.first:match("%?script\\") then t_error("ERROR: It appears your subtitles are not saved.",true) end if res.filter1=="vsfilter" then plug1="loadplugin(\""..res.vsf.."\")\n" text1="textsub("..quo(res.first)..")\n" vsm=1 elseif res.filter1=="vsfiltermod" then plug1="loadplugin(\""..res.vsfm.."\")\n" text1="textsubmod("..quo(res.first)..")\n" vsm=2 else plug1="" text1="" end if res.filter2=="vsfilter" then filth2=res.vsf ts2="textsub" else filth2=res.vsfm ts2="textsubmod" end if res.sec and res.filter1~=res.filter2 then plug2="loadplugin(\""..filth2.."\")\n" vsm=3 else plug2="" end if res.sec then text2=ts2.."("..quo(res.second)..")\n" else text2="" end if res.trim then trim="Trim("..res.sf..", "..res.ef-1 ..")" else trim="" end avs=plug1..plug2.."ffvideosource("..quo(vfull)..")\n"..text1..text2..trim -- vsfilter checks if vsm==1 or vsm==3 then file=io.open(res.vsf) if file==nil then t_error(res.vsf.."\nERROR: File does not exist (vsfilter).",true) else file:close() end end if vsm>1 then file=io.open(res.vsfm) if file==nil then t_error(res.vsfm.."\nERROR: File does not exist (vsfiltermod).",true) else file:close() end end if scriptpath=="?script\\" then scriptpath=vpath end local avsfile=io.open(scriptpath.."hardsub.avs","w") avsfile:write(avs) avsfile:close() source=quo(scriptpath.."hardsub.avs") end -- mkvmerge audio if res.audio then if res.trim then vstart=fr2ms(res.sf) vend=fr2ms(res.ef) timec1=time2string(vstart) timec2=time2string(vend) audiofile=target..encname..".mka" audiosplit=quo(mkvmerge).." -o "..quo(audiofile).." -D -S -M --split parts:"..timec1.."-"..timec2.." "..quo(vfull) merge=audiosplit.."\n"..quo(mkvmerge).." -o "..quo(target..encname.."_muxed.mkv").." "..quo(target..encname..res.vtype).." "..quo(audiofile) else merge=quo(mkvmerge).." -o "..quo(target..encname.."_muxed.mkv").." "..quo(target..encname..res.vtype).." -D -S -M "..quo(vfull) end end -- batch script encode=quo(xpath).." "..res.encset.." -o "..quo(target..encname..res.vtype).." "..source if res.audio then encode=encode.."\n"..merge end batch=scriptpath.."encode.bat" if res.pause then encode=encode.."\npause" end encode=encode.."\ndel "..quo(target..videoname..".ffindex") if res.audio and res.delAV then encode=encode.."\ndel "..quo(target..encname..res.vtype) if audiofile then encode=encode.."\ndel "..quo(audiofile) audiofile=nil end end if res.delavs then encode=encode.."\ndel "..quo(scriptpath.."hardsub.avs") end if res.delbat then encode=encode.."\ndel "..quo(batch) end local xfile=io.open(batch,"w") xfile:write(encode) xfile:close() -- encode if res.tenbit then ten="Yes" else ten="No" end if res.trim then tr=res.sf..","..res.ef else tr="None" end info="Encode name: "..encname..res.vtype.."\n10-bit: "..ten.."\nTrim: "..tr.."\n\nBatch file: "..batch.."\n\nYou can encode now or run this batch file later.\nIf encoding from Aegisub doesn't work,\njust run the batch file.\n\nEncode now?" P=ADD({{class="label",label=info}},{"Yes","No"},{ok='Yes',close='No'}) if P=="Yes" then aegisub.progress.title("Encoding...") batch=batch:gsub("%=","^=") os.execute(quo(batch)) end end function time2string(num) timecode=math.floor(num/1000) tc0=math.floor(timecode/3600) tc1=math.floor(timecode/60) tc2=timecode%60 numstr="00"..num tc3=numstr:match("(%d%d)%d$") if tc1==60 then tc1=0 tc0=tc0+1 end if tc2==60 then tc2=0 tc1=tc1+1 end if tc1<10 then tc1="0"..tc1 end if tc2<10 then tc2="0"..tc2 end tc0=tostring(tc0) tc1=tostring(tc1) tc2=tostring(tc2) timestring=tc0..":"..tc1..":"..tc2.."."..tc3 return timestring end function gui(a,b) for k,v in ipairs(GUI) do if b==nil then b="" end if v.name==a then v.value=b else v.value=res[v.name] end end end function esc(str) str=str:gsub("[%%%(%)%[%]%.%-%+%*%?%^%$]","%%%1") return str end function logg(m) m=m or "nil" aegisub.log("\n "..m) end function quo(x) x="\""..x.."\"" return x end function t_error(message,cancel) ADD({{class="label",label=message}},{"OK"},{close='OK'}) if cancel then ak() end end if haveDepCtrl then depRec:registerMacro(encode) else aegisub.register_macro(script_name,script_description,encode) end