From 80d4ee23d33df9a4f0d9d0e41370e1cde8224d08 Mon Sep 17 00:00:00 2001 From: John Andrews Date: Thu, 19 May 2022 01:48:29 +1200 Subject: [PATCH] add PreExecute to Video Nodes started adding Video Encode node for basic settings --- Apprise/Apprise.csproj | Bin 2760 -> 1338 bytes BasicNodes/BasicNodes.csproj | Bin 3778 -> 1839 bytes ChecksumNodes/ChecksumNodes.csproj | Bin 2872 -> 1399 bytes CollectionNodes/CollectionNodes.csproj | Bin 3256 -> 1585 bytes DiscordNodes/DiscordNodes.csproj | Bin 2870 -> 1393 bytes EmailNodes/EmailNodes.csproj | Bin 3386 -> 1646 bytes Emby/Emby.csproj | Bin 2844 -> 1380 bytes FileFlows.Plugin.dll | Bin 41472 -> 41472 bytes FileFlows.Plugin.pdb | Bin 22132 -> 22276 bytes Gotify/Gotify.csproj | Bin 2752 -> 1334 bytes ImageNodes/ImageNodes.csproj | Bin 3232 -> 1570 bytes MetaNodes/MetaNodes.csproj | Bin 4504 -> 2197 bytes MusicNodes/MusicNodes.csproj | Bin 4228 -> 2060 bytes Plex/Plex.csproj | Bin 2844 -> 1380 bytes .../Audio/FfmpegBuilderAudioAddTrack.cs | 2 - .../Audio/FfmpegBuilderAudioAdjustVolume.cs | 6 - .../Audio/FfmpegBuilderAudioNormalization.cs | 8 +- .../Audio/FfmpegBuilderAudioSetLanguage.cs | 2 - .../Audio/FfmpegBuilderAudioTrackRemover.cs | 1 - .../Audio/FfmpegBuilderAudioTrackReorder.cs | 2 - .../FfmpegBuilderAddInputFile.cs | 1 - .../FfmpegBuilderExecutor.cs | 13 +- .../FfmpegBuilderNodes/FfmpegBuilderNode.cs | 27 ++- .../FfmpegBuilderNodes/FfmpegBuilderStart.cs | 2 - .../Metadata/FfmpegBuilderAutoChapters.cs | 4 +- .../Metadata/FfmpegBuilderComskipChapters.cs | 2 - .../FfmpegBuilderSubtitleFormatRemover.cs | 2 - .../FfmpegBuilderSubtitleTrackMerge.cs | 1 - .../FfmpegBuilderSubtitleTrackRemover.cs | 1 - .../Video/FfmpegBuilderCropBlackBars.cs | 58 +++--- .../Video/FfmpegBuilderHdrToSdr.cs | 43 +++-- .../Video/FfmpegBuilderRemuxToMP4.cs | 1 - .../Video/FfmpegBuilderRemuxToMkv.cs | 6 +- .../Video/FfmpegBuilderScaler.cs | 6 - .../Video/FfmpegBuilderVideo10Bit.cs | 2 - .../Video/FfmpegBuilderVideoBitrate.cs | 2 - .../Video/FfmpegBuilderVideoCodec.cs | 4 +- .../Video/FfmpegBuilderVideoEncode.cs | 118 ++++++++++++ VideoNodes/InputNodes/VideoFile.cs | 6 +- VideoNodes/LogicalNodes/DetectBlackBars.cs | 6 +- VideoNodes/VideoNodes.csproj | Bin 4098 -> 1999 bytes VideoNodes/VideoNodes.en.json | 14 ++ VideoNodes/VideoNodes/AudioAddTrack.cs | 6 +- VideoNodes/VideoNodes/AudioAdjustVolume.cs | 10 +- VideoNodes/VideoNodes/AudioNormalization.cs | 8 +- VideoNodes/VideoNodes/AudioTrackRemover.cs | 6 +- VideoNodes/VideoNodes/AudioTrackReorder.cs | 6 +- .../VideoNodes/AudioTrackSetLanguage.cs | 6 +- VideoNodes/VideoNodes/AutoChapters.cs | 7 +- VideoNodes/VideoNodes/ComskipChapters.cs | 5 +- VideoNodes/VideoNodes/ComskipRemoveAds.cs | 7 +- VideoNodes/VideoNodes/EncodingNode.cs | 39 ++-- VideoNodes/VideoNodes/FFMPEG.cs | 6 +- VideoNodes/VideoNodes/ReadVideoInfo.cs | 6 +- VideoNodes/VideoNodes/Remux.cs | 13 +- VideoNodes/VideoNodes/SubtitleExtractor.cs | 8 +- .../VideoNodes/SubtitleLanguageRemover.cs | 6 +- VideoNodes/VideoNodes/SubtitleRemover.cs | 6 +- VideoNodes/VideoNodes/VideoEncode.cs | 9 +- VideoNodes/VideoNodes/VideoNode.cs | 169 +++++++++++++----- VideoNodes/VideoNodes/VideoScaler.cs | 12 +- 61 files changed, 368 insertions(+), 307 deletions(-) create mode 100644 VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoEncode.cs diff --git a/Apprise/Apprise.csproj b/Apprise/Apprise.csproj index a62295e1322304f200bcaf980d5ad18ba2602ce8..850537e7ddeb9836080881972898c7df8364b648 100644 GIT binary patch literal 1338 zcmb7E?{C^L5dB>K3M;BaktT;$Q#Vb)5-aQ0s$~f1K24fTa)66tTmDhRf4_5*0tM7* zUo7R_yLY~K=VT+RpCR{TQmlH(qsXmxdhY4)+ms@duvUwZ3`qv8-+Z?^7={R9Q*IaF zZ!Irjt?h~_@Yl4PrH6RUzAKFsx$sX;sD)#o_)J2U9>6ts7^IBhEo%$Z@v+u^h-IDO zIj~zH;a2LkqvscoldnO@qUkAe=opMd+C@T=4&GR34LCZ-R-dWoZM9Z4BiAk5EjkRcEkE|%{S=o=_po=cW8q}LXqR>?hu#;O zRLSw^7u%CVqztF%c5e0wzj~G^rwr!P-Fam5C{|v0& zg;Q&ua|+WgmF~mr0G48Pz7Jfc%y^ zB`EEP=Q){==A)~-44SY{$ztp}9y5{#GP1g6ta|({M&Qr}NotBg9-`D4qp{O-Zz0lh z%YY|7(r=fF-~Lt=f%Sc>;N65x1`n+yW%`lP5TwYO+Y=*a&n|MElV*Gl^2zXt`0u@D_8qjD<5m?&_6ppC+@yaKc!#oBGR{3N0Vqb^yZdK!@5}2 zH)I+*Rm>)5Cfur82tzw&c|LMGi!>I)*DFz-itMS|)qOOne?WFR1B2Bj77<A9eh$)GHImBfL7P@x-E0gjU%Ac`$NIzbGWd-F5^cJgiIGo1!zarXh zO%BkS_JSg=$Nu2vGWKlgaEMKf-`Bfyv|?6BKU-mu}==sUBrC}FJ@A)Zu$am)8c*A+tqu?aVGaOZ}X@Sx2-Q{eivGfcPf$i65m zDRSX%E#|ij6rV~Mrdx32Ue!|i?GlZ^Ds?vWq%*JGfgr ztvz34J(a>PCPtJc6wH|#1#W?Xc&f{at0nUZ&!v8_!}JLX`=i<|w6W|`FBBQ6!hokd zp)prf#@=!(a&n~$Kt>~qMF!9B`5X}Q?YT9QEu5?Dt2CWq+L=#Fb6uu*>=$MTa0WIv zqH@^&CXMoG$V?luQcek(EQBSMtmi_JTr044v5IpIpgbve9UD&2j_U3ST469y44?x53sji27@)0>li_ayuf@~Z~y z9L3e~CZ+e%uLk|*H)!@`q~hc?@&A1_*C2sg_>bzrhGTy&9F)-u!rFH%4&gP-z@W_l zxlp;R3&@g7pfSU?(F%$Wdub*>i(Wf6aR+|F+m>KLQx=EN>D^05gRG`;>*$tRG_WBh)w+4)!%ncx4YXL zg~Y@MA9lODGv_-q-(1f0_pgHO*$=z1YYXhV)vRM(Ygo&gwrk6_VWF+_8z5;~h3{3Q zO}lW?zV)nbYiMnuvu8ysSP6udeMP^5G_X@vTsiIctgJX`7mYg7&`#`)mHG8r%OE_q zNA3z(8>6+IT|FDvTerS%?V+Z$yk^-VWEW`m?2xAjpK5l?P963yVzVS$&Yyzi*mb_X z>`2g$Kx@O1JZOM(=2#LBU9iPXWar3h*qpQZ+F9fi>$C29w0;t8QX`9Sh(+4@6IT+w zB5@0yzlverHf_h*HcLB;>7!W1HbrqUL66 zCg*FWF<%pD#V8^c15jc=580I+$qnj?<__7``-6z)S8`7#zO=7FoFKAx$U z1kT1L_>p^;{G8{QGexvI)KZqnhJY((GGJwkFKza2fhBf@5wp=H|5eHIY?cMyDn81`%Qft(f=8%Y|Jnx&-jMn6+pR^@@ikP%k zzUW#aeMUX}o<+r$ zXF)tX%%)S$f_U%vX`gsT?xa&bO^;!N)7L9j#f6^!y zmEqH}KbdgUQLB#E(CLT#6VSBZgsm*DtSGpXs?8ZwM&|?dq-jR?1L{dGXPQk$*x7$Dn?0#`?d1M`?PrN-Iu$?x`$5Vcp93{)4O4RQLbx7Es}wueVICG zrO`s7p???RYZ4>PijT*s>!tdSqv@eC*2^?iN1*A{5#n9iX?*EVvXSJ37IFe_l HV88YceArBx diff --git a/ChecksumNodes/ChecksumNodes.csproj b/ChecksumNodes/ChecksumNodes.csproj index b69b792b8d0f60239852a7e92edbf18b12983880..761883c0e20427304a5bc271699fa43280e4b4cd 100644 GIT binary patch literal 1399 zcmb7E(Qeu>6n&;&VMUYJ9_G+$>ZU1ig=pDYX$%#*r@a{GCb&3ucYEC_ru#;Y(G+=T4%^KHupqI_LtpLZC*YIfUifO=eI>^#PJaDh;MoA&@uJz6> z1I-r-vh)BB%!yMfZ02KRWP@*uAz8o-+_TQ zGIhiCngz45GLLPRc2L+JI$RptvKzD1WT*=ZfzhFRRk6_$#Hw{QnV14RXIoQj=9G&S zuV5R5JFD0dv1z}iY3XgtU`vImQ-A6eGk9D=E39nL=Zr;pf=iC1vF1S*;1@vnLMn-C zf`(Y92`8efhgV$c7CjWqXqN7)`Rs*CUxY8aMyO*FiO)eHgKeIW{=4^nS<1>;9-t3> zlINs<2@6;CLyCRdH`U)l3D!>nkZo2DV?T!$WA@3mnSOdc(?J-Z^ev%TJRCm5f;R{@ zc@{aNXY|W|Y3v5@EE((o5$ GCw~B9N7bGH literal 2872 zcmcJRT~8B17=_=Xcrm?3Z0x-y*xVmW^#H{ou@}_!SKwU+LJlY z$SF!~W}n>7sg33sq3V|KeFs!($Gjy((y|-)$DCS0saAGe6lE*ji}$CC`2?mBu1Iqa zjx*u3HxfH%y#=;(4?3`(+LS&0`QBxwWklG0;_jdiN0W2+9n%4^4$vn>@<5T- z_^Xe@N8D`cT+v-r-1~H{SoisOuAr)l9y!s&nJ#Dh{E#!pyz9Am@+SDeo^kwuC*)>@ ztc=~gDnqyJoqcfka2Go8`TT66S)Odhx_(cdJMTmty*;u>8QEOxK@k}kb-T*Lgxj6L zk;Qk6uS4>D;-W61!v1~YOt}?hh4S_bZ(X<|vS)y*G1@YgA~vur#$T}mI8@)4a8A(| zQOD{Gty?`se4Z(B`w~~P9+&lsGt~6a*hO(l%oF=aWtGTdpGEC?%rPADrR1Wo;*PYe z;|vN8ol5ng9%rO{s(vcD2qNcL!e<0R-Rv5aqVp?ys`jNyonhv_AM!cI4SAdJ+gS2Q z`Vw{wh)mokrDS==Jxq>qp@zHi jwC6fwqflp>e+fJjFyguooiPi$9Q`J)YUkpV^1J>55O>*q diff --git a/CollectionNodes/CollectionNodes.csproj b/CollectionNodes/CollectionNodes.csproj index 14567a5858ee683a593caf784b42de2ab4105567..2785f805363f98ac12650b8b987015d45185ac09 100644 GIT binary patch literal 1585 zcmb7E-*4J55PpWg!ipxbJrj!(_V~o0T0KHY^Rj}es@U=l+jju zN#yf)_uY^0oSfYin%#o)%9=sczemb_Gr=&~tdijf^5BNR=eFEj zSt&GrRupOIJrr}Tz^B1gnC5ecnyx~$U(TE=3druACXbB`Q&9@DNr~h_+qI|Ajft*@ zRajc*({FZZ*wADS2=QR8iJH37bE`GZ3#ZD)HUmW6*HsT@BIHUe08KR18>iD{2&MlN z$2k(`M9ifvV)Xh4Nh~Aq(y5Zdr*(K1mLjmy*;|zX9)O9t(kg6Nu*`wTm@pYzI~9QC z&K68=^Ygsj9%`awK3Ip-XEtY<2!ed+KNp^25&;d?2hZjZ)&Pc4stoQxR+Nk9NxYvL zijvBHq{`$nKv7GSom-PB@*6~~^XfGobG4|PAk&);8w^-y1oLXqA$g-@npRvx4i0w& z*rk!W%HVJxX5d4;6zpDIxK|%|msTr&DbTcbrq3`FWdIlLg7kTxfBdf)+s!*31-I*= z7ns+sF?e7s8CwJF8VY-h74GCzkDG(xf4ntKHqXbwwgxa^*R7{w)wD=VyEL;us! zq#O#=x=lLNYq6=<;n;1RF|JBYMa4W@+S8L9Ppq_i-8$j#c)zIwf z>CY>a`CbrwRe7&>!cSgoS1UQ9u8FP|%G4TcC0~^Z6A_jv=fAg)1aBZb(>jT_2-IvV hnU)|`uH?f*cOkUL_v+A0`i{5UhYhT^h+c}c{TD`~5o-Vd literal 3256 zcmcJRTTc^F6ovOICjN&BF+m@k3Irsklr$oWQLGkx@};yC3cZC6w?D6bYagbk7cK=f z&CHz6*?XO}@AKz((KhVTI@Y$t&h5ZXZD1|yS=ZKW-WDyjH#`$|x>o0XgWawj`tF%! z*0k41ts=8wC3|kqtY{TbdUlLX9f`!MXq~vmSG4QCJ3ykzZfZOB!FG8DsrhS^xgk#9 zS!H%*A6@gz`a>B>?OKiZhe&0%#j1ui2X+DeDW{fTtDRN1q-ceEShzoKn})WxP`@FH&7s{X81hZw`n3BoBZEz6sqWC?jEuXwP-(=1#JyJI zo*g(3p;wMBE?GedgbqX+~v_4lG>Mt6a1@lGS_W7R)@D@Kk35(6(yFs%Dx+7 zoZyEHdEuxhB&?g<8=R4GiIdLG|L^loxjt@1Ud6&1u(yvy2D<}(%81;z>cE#=bO$WdvR}P)vf^UTN|EZ~onxY^ZXnjtM^9y?v%4N0-EYdZ zhq4>BI#@0Q&A?rr-@JDFe&3{Ol;@hd>LHpV>Mj>#-xb!%wuk73{0(OY&1hN9>Z$&E zOl9maOEmp^9=qxMUm?27UIXK?4VFCS#H#yGHP-=m)4NiQRn@-p{SkWp&DkHmc2mA* z7frlY{#m}M*Ui>RD&&AVrDn=#uN1;3f1BY^b=o5`Cgu0*hVPFQnTV6phg4&V|1?J4 r`E){+TvblCe74so`jx(P^cANmCan9Ub7aX*TR%%yN^|^MBxwBwgku$$ diff --git a/DiscordNodes/DiscordNodes.csproj b/DiscordNodes/DiscordNodes.csproj index 03705af6fbb96e7c7c1292b004adb1e08ffdc600..8866143c5e7e18a13bc1a885022332c858f4fd82 100644 GIT binary patch literal 1393 zcmb7E+invv5PfdG!m=u*mB5=;s35fKDl`;|XsFVZCnV&qCy9yIUis3Hzh|7?CTSXq zcv)%BoH^sU=I6gFO&-8`Wlcd(SdS7grIpG|3mU`Z^~o7M=lPAZzn~1{R?Z80tx9LT zodh=gIA#b@-supMNHw^y{N!w`IU?cxpls;adrRk-sQ z-*YhHUPGSkzzug8wNBVy1@C*$5<*lJ4OT&v}xAEL3bYV>Z6Udb*b(DV-YxV%;QYXjuTMTs!1Ld$- z)-5sTg;-*>_ggV+dPp1T`Kt~#*%2g3mFief(mg!F--((=ClYkrB}GBH1GtaVE^h0= zx^E+xfWwW4O&U+vdkj9TcAT^mjaT9=>!m}1WqMiYcIBKteJSbyj&4Lw*(rPXUmEKp zvR~z&{o0diTDj&b+~_z}21}DFjy3fbh^BxKBiE zKpQ(=sa@GS7eBS}%u`<8vu1T{9XwMzVpXT6mR*u*LjDq*W!ZK0>|2qn>;1(eLq7&> zOpjDS51ez?CG{ZzTfM2?@yb{#5Te2I9Jb zS77oP%uHh?)D^p3kpzx|3zqG`u-)=CjHpo4-=<(BbGA zfvMZ5PtV2Uehf2qd3>Gv8hJiBy^q~fbxYrxGSaF}k9Pi&9lfIFaBl7z8)^=_m#-^h zCfrqB5r!t}vORK>N6PELH@n3n+jLXkXVdBEP@V3`;I)lML?2)A++?>~%%3S~+W; zR%JR@cc)x6>8&S!vxv(MEcDsSY5jjQS$>1LADi3sW zJ7`T)A&={^-#c5%OqUM3_{98uncuUk&|-E;vsX1PV=SJqH1ZZDpv?eVR0x&k>ba*|X7>ZcCO-KNhVBG4n(Y|CD!PuSFZpaI;B~O73goJ_AOG}TAZ^xg!kXg+f zF!T;eiLm2{M6XEnJ32W@;->`dRx;sc25w-mV5!Z5DRAeso5Z_ugx(aT6sd6c7RRv+ z6rV~+;+-%8hhEB%+dwt}OkV18^czS@lFgkf?Z-IIan`(Azf>177Cl2^`4!mIh|-}U*kmp& zDW#tYMYs%=WTicEnpjX7;fg>wo-F6cOyB`h?@)EZN!W6l#M@-SBB9T1kKlPX4vNY# z1g||4;oM$CI!DL&GZ!;&cnF!w`Mv6v~ywg}!e1c%ycC41T#Rl8g3@u* pnn`rg27z+4l5q*C$VK|-`T>^~U%TCKCEe!iXW~^mAO1X8`yX35Dzg9p literal 3386 zcmcJRZBJ7{5QXO}CjN(;#so<8Rw1G>P*N2XmD*77lZh#}Ev@t|eG&Nc>T`Bkuf2Vt z0*17A>CT)zb7pqt{`$FXyLM?EYg=SLtZoAvTFZLYwH?dZGmGsh&xoC_CH!7vw`*r^ zcWfhT*b}U_u-UbOZCe?Ho_)tYVK=f09_P;b5YNQz4zXyk8(Yct?3kyw%7O6E9=K10 zZ$RriUL(7*cg}xo{cB5EdCMa(dtjEVN;Gw%=(9>#6>OiA+pPBa?%4%zU4D)~!+Yz39JU!Z)QzK`OIX7qD_^k7{ZF%hGGf-43SRI2hK)pIl_gQ(a7kCa( zserQD*>b82^CtU%dqB+MD1}=foVYA1PeV|ZyQ-Z!4TQSbI$)MirtRYPp3^wf7Qy!o z5%tN}C!Pssx35>{e{xZT*$`$O$E(FkOh@Q2W-XSt#RweAAc=VkSmNV@vc~b(5h~}t)_u)S zYPyVlzz(^`=TtMSoz`1Sz6ox>U{pHPwNHk+lsNlW`ZQqz-J9PkCi` zQ!iE8IhN`#-YWFr6V7eJrU|zZu?_7NNO`K%S4o52&>0gKfByQcn=!-rZ#i`c1KqPe zr}!yS;N~@14^coJY79G7X#`Jo7k_Gm(mnPf{Ca$=Ge|92$yR;BD-fZc@Koda{gO}O zH21YHkBFRk$_XsI{22b^a zV6~1##BAK*d)379)akRzRPmE=#Z8SeK51q(j~aYbNt(&hVX~iSaxa>;$M_6kk%=p2+{kMz?di zKGJs@y#dVI_nDrYC$sXJ_JR^OMUzNxGpdA`w=TU|bxA%9nIcN={7_~`EF?5u9isrK8S zT0vQL!&CHBel(xYSk=kfY!1)oaJZXp%E)qEqfGWght5{fd)OQ&X|ue zHYrX_wCePVVHzXUj5t=PQdMKi&7~yM2Q-N_Gc+57b(3{KmhE)(%T}bkCugbm{0*Q` BFBAX( diff --git a/Emby/Emby.csproj b/Emby/Emby.csproj index 6641c68dd3bb06bb4e12796184b34b40d9162a44..2ba1e5563aa1d6a5993a7007341b500aa220088a 100644 GIT binary patch literal 1380 zcmb7ETWi}e6n<`gg%E|bu*qo|-59n?*2~thW+lz`G#H9~604CVqf0aY`yJV7lBS_! zF9v_-JKwo0J^xcF@(9*RqceKK29$u#O(FF>qruPKo}AHhp59sW0J$gk#Ui7(GPlN= znP;P`2}6kTUXPGSs=@l^vo)dSh#)o*b`Jj9iVBv-F1QAN#s+D!i+Ajss#Y?W{)fZ( zj)N9c1!=Macid4>Dw@}c@ zLl&dIxFL_}R6J57-$yVW;n~lx-MUC|iWw?;-A3kb7b3=9|fd!p|b= z44s2&sw6n>ZNSoBW5i$q$*!DOSyfQ1e0IP#PE`xtY`PGII|!}A;7em2|7uE2hPtq@ zbZPR0#wcA${HmHZY4Y?J4I*ENIUo|Zwh44UPaI%!XBv=W_OQGk0X>M!a;M?@Jw=iNi9;)r7 z92pH{43&9=j7D7snmst9DM{kq;>K+}_6b}V?Lm8TqjMFCU6F3}Q6>&=V!j7o2Y)Fo zw#e9q57kmMnE8GyhKmWTBD~DzU{ei2QXymaGD`Y~NBBFF^I%1Uj{79bNdFS1Vcy3B zJy=g`3^TBJefTep z^}DfO<)3rfQ);}j?jg8qmknDJJB~g7BDKF0zT}Mk+GVjqsWe$LRzq6LzZ_G$hw&(Q F?=OCn$ov2R literal 2844 zcmb`JZBG+H6ov0sO#Ba<#so;TRZNf=D5)Zfk=j7)}mGIoz;1IuL1~S?c&+B605HE7mpPE z2(&RdQUpD4&Rmw1hXicpreWvUTW~JfYVRy^)uznr-^|aWtymp$ACZlO_@CH@9h31ltSoE1uxA0MXeZ26)E;fa>7Zaw$xFe$ac%NbysH?9 z>l$8x$rmtFjg?VX(d~+qwq-AE$008B4rpFwF%d{*55rtNCm_EiPh*t!iD#3@`}5J| zO#~IPm=MLl?RbxoG%z8mDP!g1Z!rRg8c0%859BUNy<#+QdX5)XTJ0I|#7FvVpwro% zstBxadxhB**i7J|N>ZdB8FfL5tinC!t?DdskuUlNjBM`9RK2n{b8{AL=^W|q64t6} z?-lyW8LRf;(uLWC$P#-^-b-{9ulF|Apm%kx0XD^0b6Q@F8P-2=JFAY>V}%KKb;Xo> z#M?f(>0nVdl%odsh_~uB38NWvraaZLVrOmfFGL@aH+7RP@BH3GL@X3_7U@cym+=r! z-}@Aoy6*Bmnfe~Fp0vBXlU7}v;;H%{@K5K0PJ_U^$p0brdI3Y_MCU_^%%rQR2j0C- z>a)xv?Yh$K(BbGFfvGb{IgjsUl(Na|mkiP?M? zLQOwpZiq4LPBxB|nRa(s7KEX`nYTx-zo_zh@J-L^$kttN_p7NAIz*?FFnDd^5wVkd z+}EA1&hh9l4kz!LTQgfFQbC(b8B+eGjQmclI^FBcNnnwl^U*@j_TL(>@X&eSzc_-T z{)N*idmYveql&9URkUJ~cC+V)xNN~f*G@*;|C>qvJLS*V+@~L}zr2D{1$vK%?Ql4g zZ+}bFZgs$*p-^ug`vW>u^-5FfbY-$yw68a(=`yq;yQJ?T#hAxftgw6pX^i(}N%rjN H;^+Pbfa}d1 diff --git a/FileFlows.Plugin.dll b/FileFlows.Plugin.dll index 92fecd0c5d18bf684a44311b41edc2f21fe1c060..de5e5245a8e882f86be9c00ec1a4f404877508bf 100644 GIT binary patch delta 12344 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 delta 7840 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 F{SOx7eq{gv diff --git a/Gotify/Gotify.csproj b/Gotify/Gotify.csproj index 2c5aaf0264ec23d4705c361d19874bf2abc5c183..6ddb5ec0ddc0d8d6f7f4b4337fb00fff4f23ed5b 100644 GIT binary patch literal 1334 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 diff --git a/MetaNodes/MetaNodes.csproj b/MetaNodes/MetaNodes.csproj index 125d5b5e5d7390ffe485aaab6a6ad6929ba8b940..a84a28b29bb9efb64390111a8ca7943fa16dcd9b 100644 GIT binary patch literal 2197 zcmd5-U5^?!6nzfA!a|Wqq}BuMmR&XAh(Jg;>QWTg_N6c0nLFU%u}8Ki8>;;HUfToA zvb5Vuedx=8&pp1M_VMuT*FusnV64z;)b7!4n}AAnCe(V=u3Y|oZ_s|rh6|%VL+Z#f z+m70|A~o9T+|l{_6-B7cS}j66sRZM8AB?U_h6rLSZr0#th8OUl&6X)}1KJ%Xr+8%F z6r~iYaCa8tTLy~XOBg06aOBP_DZTl?IsmN~!yA>#Duc0<6RjL5H`8VbGJpTo8J;@k zOy&JP!$L=k>Rt-FSz-txxfom8bj~+sV3gKzp=*iRCuoNW^AkRqD=gPb`?k zM3=jtb;>1vw%*m)S|~6vuys0r$2eB8GNLFTdtAaE3~`E*s~z(pW>P=cVKUJXLhHD4 z8*ME6s5gp?Rc62w_Nb2%OJcX+IGO7V(7eaInWtO621NW(x-*h(oGa~*Ns?o{+{dG- zE)v}Igo)dyU{fPXhX<97NhzzfP{eJxBPouU3uQ?NEL5&d!99;Cp9HBaWW;GAwcb`G zDSUcO77~31mbO~{v;P+FjT7Mg>-DGjs!D|}sp|~2{2=64HQ2p~tIH;(|ASv$ z4eFn(+9o3v4}0zZzpv&KNZ=O!PxYYGI4FYRH3vhUOr}FdBNFG~2l3)?A3i}21~nH* zEk~yUy8`Qi%92_vP^YhG*GqNsoTFvf>_6j-mkvz(=%i2k^!i`UFDR({Uig#d z=l=-6wZC7e#*I?(G6t!jpy+3-ReeYUx7@AtFO@4RHx&joLz^8N%Ll#-!gS6W4>+n; z2*Y9$&!<%j&Y4l*&21Fx6+N~+T%^C==9TE3K_&Tv-g-%h{~Mkzae%$h{AWDP;lXJY zUfYkpJ;(X=E*d`mLGRC0w+JeFZ23<@xdnF{M(OW|M}S5a&7>SA2S(^LPH9;}DsqwT cAF>&j7N6ErxE{00G;hr!u zvJT(7jE?Nwjow&ceftWjXUM#;%nqyx!q|RAzQbr@XUw>C(kIO9xX~PmKBK9f+G}fZ z_flIRd~Tn)f5O~=cJ1^k?9RS*^Ka~WDk&*1*|Iic=SUWI%u^eidUnf76V}hrStHM! zJqK1})%EhyBceAzyM`lKFaYPBV@W*ZV2hi!{la(;oolvUx{Q2kH_RK(<}afyO9a-?h9GoV`Ox9K!r~DcN+&TP`_FhA!uV@ET{v#!o6J4m%ygE2?~fI9YNCyp7{JU0 zEBj~>GsisZUS4+-`7&bx=SQ$&v}VL<>inyi-L=QI?|iXJx{T?gwu|m!W;5jVa{?do z$2F1rmU{;;52sI;Gd>PsD#wr&3!R~4rPv8AP-ng`O#hLjYY|TCw-I; zZDQ;WguwAJ){V)z+%dl>6=ok|S3ztjGL=1Vc)9>7A%aI}G(oR`u_zlb%G_sm!khwq z5-N&nlsHr-F3QFEZM<>G3{?y-6>8QyG#H}S1&9S!=Jqv6HS)*DZ+~WG3Wk`dP1pJ` zW?28oX{c&AV1}1=Z`+Ya=cgmulz?r~I#{RxXb*@k@m>8 z%hmt=RFxsINAx|AdY!Ae&Tv%*B^%_>Ho2^9k!;MDm>~~b-_^uFarcnV`uw$TsfSYS zXt<})O(}oZb-yj<K^tu-&a?A*!Li+J=#gM zLrOa3G^7*H2(HSg5m_B#3z_e!&+0jJJ)+7hrqq#YXIH1C9{(2a+<_?Tq}8T#gSBC| zDqED{=Vb2WrBlpmP6C$BAgiVPO83bYzeiVlJM{VGEYMo_UoUi0FX7quSAXEQ6a1jQ zphLeAzgKJZ1bJyzFJ;81X?2SCZ=LtmpM*2>n^`nfWoDh;W|b!YWIk+G_0)}oUbZ^= zzk^@(j*1fXx*6K1_H)IVB1I;mrR12-&9Z43BgALq|BO6QE)85?*IB4(I>YE}rVd$% XACjKOm;5ptG1J$vuCemz!_WI0ap%wF diff --git a/MusicNodes/MusicNodes.csproj b/MusicNodes/MusicNodes.csproj index d9becabfcbf53754a097c6d39f331ce587c1650b..592a2e109ae72706b8b84734c2cc317858cb2656 100644 GIT binary patch literal 2060 zcmd5-!EW0)5IrYfK^O(x9!lBXt`lrz7KT%|U8Jc|JMCpJj5M;@P^3UoO%3$pJ44Bi z;utL!*u!2V&ExRR3_0_ZzW!cn_6?j@)=YXsKIkzpna!11PkMDIUJl2-*CJgw`xUal zmigVJ_d#XOdRqj3{bt1x>WR}+#>lAx7q)Mmtt){DVk_y^5av#naBtn6Fc8LkkS2$C zdS*=k6Xw<}K^Jen2Pd;2 zf~$%8H(sYF>7u^X%5RpKl7_j$h|SigcoUdhJ~t;S27JtieDn${?!t{Do>yLH;fU|S z3MMhL)%MC}Qsd)}sHWaSiHU)4)A^OhLlrBh$`bO&CG5Z$r>vye3JNjT_THz-%r+3N zPV2C-&Wpe8#;~c$9gxEgr6^5F;zR9K#;$D+xV{5vU1oQ34Tw-X-8d~aAyodaBq=ap zK>>MY%LF$)@x<+y;4`PHfXyl*IV-GVm9rUmY8h6>xWwf-W2=qwtkU&b8J1xaNM$@T zloSh=5N!O=-9vD$iUJ(=3vrh+5D~?qwMsgV9~)>lDbx61KCE!IWRg*Ao+jN)@wVe3 zbccg5JcS2{VVCBLDx38B&z_@IsCDfmAuoPBd%n;zP_P&M$)tY?xApqR)NE&qn?6oo z9JOovMLHvfXM^DdK1LU_<@tDc9$yHN)q;aR8tYZpW%MUmXXEyDc9AA6V?$5mc*Ixm z4Ya_1eq{gmC4K@8(!>A0$dmMafHF#6_X^pQJM6Q`gf%xuS9Y==hEEXtEd$ny(V@bw zz=tTatd$BB>N7qdtxkSNw2YhSG0kNa!1W$TMtsE2{}snx$B(n1obJw|`6G19*rM3g zq{N*T|`BCDD<5&VIjjtaQ@F;cX{K* sbFI^4?-A?6sLKj6Rj6$D@0d&N@o#nrNB1<@=RivFIO8Zxq{ZU&ClHaLhX4Qo literal 4228 zcmeH~TW=Fr6ovOImG}=MR4MS#aS{rNN-&BbX@elOYSX^(VjRbD<2Y7qmqy~ZQ@*v& z9M2rz5^ismWzUSyKI`ng_I3XIxM$DoU%Rk@W%iGC?aId1w@Vw^Q@dwdmfI%RjFq8v z`2U2}q4j)qY7;xPpOAWt%rmRoo;5(Yw7-$>u$ozu9p^6nnw=eA9V2ndYHr7NU@fje z>K+Jp?Fav6?2Tw6*K15%Zrb*a^)5QsY`t`fd~8$p4QBfn(H13Agd_LN z0Gt!f-*>d<*gZr~X(m3zLX)QfydU82i8CY*)%|`t(_CKuax7_f$Ne^SKKk%;#>pXO z#Lpr3x~Atg&|dtllTWRVIDdjw#%!Hv&E3n2+--YkyY7)y(kW($-4k>XGq*!tzbEn` ze~gIVS6n;zdQjY*&-mDfsWG-(6g+f2vgD`en=i$PI;0%8K<;yzvgBl~H|8ZHp7c>x zw28565F*EiSa(V0jh*>M#_ENUW2LX z)dA%gJmo{sVqUUDXd``&4XPYLD%P$y=rBODGxVHb=h%KBw`*ijh~U$iojDldqUIJi zK)VSup8vu%R8{PA!h~PtZM~v>#(Cl>V_lWC<2z)fs!ztLrOi>ZF|n`O)k1e=l=dp8 zd%i1kZB-ZA{;c&O$bqel|1bGR77dGzpgO+%oG7iXaLPxy7-~+``+`#@=nyoDUWxk@ zJ@S8-6BG@)R!@?Z!Tx{~_z||axIntiH`<*$|Eo9^=b=~B3Qw=@E2V6RiDi z>Ih}e+^`?*ez7LadN8kk6lEUR2bXBz6a5?Qdte*%$*26vf_Lt(51tLzpf%_@8+6&w zA-vSl8YnRlm{Z>lw3{w-^tDs+Zc)k#XT^t$=C z#hx*Cl#_5aH-Cg_d~XOrQy2bVC%T`4BDq78cx2XZ7+pUoH3YZ`h;Q5hI#(>cmBEZu# zFOhQYx#xa6J%6kfc>wFA(HT8q14=;WrjWYKXz;VQCuj7Wr($HG0*6Y%&l=| z=Go};lp#cUuSZBEHDLYni#4I)h#)oa_?jW=dgRhKr{F|vX8S28q z!llU*8l!Y2@v(x(G{+_;U$K7k9PJ!ns^b*@6OE7Gk#%EaMK&5z)l;4h`c z78$$bL$we~%zVEU!^H$v5ng6|PY F{sJA~$v6N2 literal 2844 zcmb`JZBG+X5QXO}CjN(;#so;TRZNf=D5)ZfkxHQW$;6btL*K%-0)Jk8&b_@X+qDH> z((HEInS18U+%t3c=Z|gMwQCz#-<*B7mR(w6Jsa83cC278tZ2{qc36hi;QuDpp>@1< zY7=YQGrTtO*|oB5+YtyOJIB9))!7LvF1+_4D;wUL;L*lfw5q+c8sEXI0K#K?)<)DeV*#%)UqogT@t?xXIVCVKHF9z>T-YaNYM{K z8&e}?&;#exS4nk9z*cSQc80wL=aMb=&LUTB%DVpT`aD|RBa3iMjw3i#XE`S@t;4eg zvwhf#)jszT)kw(yiLKco6@NoyS(|fv7IBJp%t~b)(AK;T3igz`6zm(`LlBa>!b8{}za?gk-KGJU;o!RnT7fK53bc`Yx;jPdt~n!rHS9}^`^#gx0_ z-#)eJU{N(xqdIrzbDOZ%QSA{Pj%!S%WNq;)X78w*rb(Co!rnOI2}P4d(ZzWY5Ah7M zPkCwTE}oOA?}6tqSTaegAZc1U-~R)C>0Hoh5P28*J)~dHVJKgn4<&XZjiMQ-T=Q{K zpG6+&)Rk_B9!GZvrp_SMym>F9luZ^-y zd-@@3V~%lmvU#M;w7ZL{APmjTyxsZvqR#8VH$AJJt@_y>qNx))WT%rbdadK(*vTF4 zt6o=UcyxGwGq72k>KXyqjBX3r0C*?@(vt&Fz+Hx{(uhEz0#B>U74yD?d$Dnx(KbvF6q5UIp#6uJ1n0;n&W*{l0AF6 Hgtfl_%C^l| diff --git a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioAddTrack.cs b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioAddTrack.cs index 909d3fee..d1942877 100644 --- a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioAddTrack.cs +++ b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioAddTrack.cs @@ -95,8 +95,6 @@ public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode public override int Execute(NodeParameters args) { - base.Init(args); - var audio = new FfmpegAudioStream(); var bestAudio = GetBestAudioTrack(args, Model.AudioStreams.Select(x => x.Stream)); diff --git a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioAdjustVolume.cs b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioAdjustVolume.cs index 187e2890..0d445a7a 100644 --- a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioAdjustVolume.cs +++ b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioAdjustVolume.cs @@ -24,12 +24,6 @@ public class FfmpegBuilderAudioAdjustVolume : FfmpegBuilderNode public override int Execute(NodeParameters args) { - base.Init(args); - - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - if (Model.AudioStreams?.Any() != true) { args.Logger?.ILog("No audio streams detected"); diff --git a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioNormalization.cs b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioNormalization.cs index 385a81f6..0f259804 100644 --- a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioNormalization.cs +++ b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioNormalization.cs @@ -24,12 +24,6 @@ public class FfmpegBuilderAudioNormalization : FfmpegBuilderNode [RequiresUnreferencedCode("")] public override int Execute(NodeParameters args) { - base.Init(args); - - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - if (Model.AudioStreams?.Any() != true) { args.Logger?.ILog("No audio streams detected"); @@ -60,7 +54,7 @@ public class FfmpegBuilderAudioNormalization : FfmpegBuilderNode item.stream.Filter.Add(normalizedTracks[audio.TypeIndex]); else { - string twoPass = AudioNormalization.DoTwoPass(this, args, ffmpegExe, audio.TypeIndex); + string twoPass = AudioNormalization.DoTwoPass(this, args, FFMPEG, audio.TypeIndex); item.stream.Filter.Add(twoPass); normalizedTracks.Add(audio.TypeIndex, twoPass); } diff --git a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioSetLanguage.cs b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioSetLanguage.cs index 61b89c52..ae7e5bd1 100644 --- a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioSetLanguage.cs +++ b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioSetLanguage.cs @@ -16,8 +16,6 @@ public class FfmpegBuilderAudioSetLanguage : FfmpegBuilderNode public override int Execute(NodeParameters args) { - base.Init(args); - bool changes = false; foreach (var at in Model.AudioStreams) { diff --git a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioTrackRemover.cs b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioTrackRemover.cs index f1d73a63..1abd5449 100644 --- a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioTrackRemover.cs +++ b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioTrackRemover.cs @@ -28,7 +28,6 @@ public class FfmpegBuilderAudioTrackRemover: FfmpegBuilderNode public override int Execute(NodeParameters args) { - this.Init(args); bool removing = false; Regex? regex = null; int index = -1; diff --git a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioTrackReorder.cs b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioTrackReorder.cs index 74643f58..338be187 100644 --- a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioTrackReorder.cs +++ b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioTrackReorder.cs @@ -20,8 +20,6 @@ public class FfmpegBuilderAudioTrackReorder : FfmpegBuilderNode public override int Execute(NodeParameters args) { - base.Init(args); - OrderedTracks = OrderedTracks?.Select(x => x.ToLower())?.ToList() ?? new(); var reordered = Reorder(Model.AudioStreams); diff --git a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderAddInputFile.cs b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderAddInputFile.cs index 64a0cb3e..9559f477 100644 --- a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderAddInputFile.cs +++ b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderAddInputFile.cs @@ -20,7 +20,6 @@ public class FfmpegBuilderAddInputFile : FfmpegBuilderNode public override int Execute(NodeParameters args) { - this.Init(args); var dir = new FileInfo(UseSourceDirectory ? args.FileName : args.WorkingFile).Directory; if (dir.Exists == false) { diff --git a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderExecutor.cs b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderExecutor.cs index 343ea890..0f80d85b 100644 --- a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderExecutor.cs +++ b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderExecutor.cs @@ -18,7 +18,6 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes public override int Execute(NodeParameters args) { - this.Init(args); var model = this.Model; List ffArgs = new List(); ffArgs.AddRange(new[] { "-strict", "-2" }); // allow experimental stuff @@ -80,7 +79,7 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes ffArgs = startArgs.Concat(ffArgs).ToList(); - if (Encode(args, ffmpegExe, ffArgs, extension, dontAddInputFile: true) == false) + if (Encode(args, FFMPEG, ffArgs, extension, dontAddInputFile: true) == false) return -1; return 1; @@ -88,15 +87,15 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes internal string[] GetHardwareDecodingArgs() { - string testFile = Path.Combine(args.TempPath, Guid.NewGuid() + ".hwtest.mkv"); + string testFile = Path.Combine(Args.TempPath, Guid.NewGuid() + ".hwtest.mkv"); foreach(var hw in new [] { "cuda", "dxva2", "qsv", "d3d11va", "opencl" }) { // ffmpeg -y -hwaccel qsvf -f lavfi -i color=color=red -frames:v 10 test.mkv try { - var result = args.Execute(new ExecuteArgs + var result = Args.Execute(new ExecuteArgs { - Command = ffmpegExe, + Command = FFMPEG, ArgumentList = new[] { "-y", @@ -109,14 +108,14 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes }); if (result.ExitCode == 0) { - args.Logger?.ILog("Supported hardware decoding detected: " + hw); + Args.Logger?.ILog("Supported hardware decoding detected: " + hw); return new[] { "-hwaccel", hw }; } } catch (Exception) { } } - args.Logger?.ILog("No hardware decoding availble"); + Args.Logger?.ILog("No hardware decoding availble"); return new string[] { }; } } diff --git a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderNode.cs b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderNode.cs index 6f844afb..e534f40d 100644 --- a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderNode.cs +++ b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderNode.cs @@ -6,7 +6,6 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes public abstract class FfmpegBuilderNode: EncodingNode { private const string MODEL_KEY = "FFMPEG_BUILDER_MODEL"; - protected string ffmpegExe; public override int Inputs => 1; public override int Outputs => 1; @@ -14,15 +13,15 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes public override FlowElementType Type => FlowElementType.BuildPart; public override string HelpUrl => "https://github.com/revenz/FileFlows/wiki/FFMPEG-Builder"; - protected void Init(NodeParameters args) - { - this.args = args; - this.ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - throw new Exception("FFMPEG not found"); + public override bool PreExecute(NodeParameters args) + { + if (base.PreExecute(args) == false) + return false; + if (Model == null) throw new Exception("FFMPEG Builder Model not set, use the \"FFMPEG Builder Start\" node to first"); + return true; } public override int Execute(NodeParameters args) @@ -36,21 +35,21 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes { get { - if (args.Variables.ContainsKey(MODEL_KEY)) - return args.Variables[MODEL_KEY] as FfmpegModel; + if (Args.Variables.ContainsKey(MODEL_KEY)) + return Args.Variables[MODEL_KEY] as FfmpegModel; return null; } set { - if (args.Variables.ContainsKey(MODEL_KEY)) + if (Args.Variables.ContainsKey(MODEL_KEY)) { if (value == null) - args.Variables.Remove(MODEL_KEY); + Args.Variables.Remove(MODEL_KEY); else - args.Variables[MODEL_KEY] = value; + Args.Variables[MODEL_KEY] = value; } else if(value != null) - args.Variables.Add(MODEL_KEY, value); + Args.Variables.Add(MODEL_KEY, value); } } @@ -102,7 +101,7 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes matchString = subtitle.Stream.Title + ":" + subtitle.Stream.Language + ":" + subtitle.Stream.Codec; else return false; - args.Logger?.ILog($"Track [{index}] test string: {matchString}"); + Args.Logger?.ILog($"Track [{index}] test string: {matchString}"); match = new Regex(pattern, RegexOptions.IgnoreCase).IsMatch(matchString); } diff --git a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderStart.cs b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderStart.cs index 93326014..6a31ecbf 100644 --- a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderStart.cs +++ b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderStart.cs @@ -11,13 +11,11 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes public override int Execute(NodeParameters args) { - this.args = args; VideoInfo videoInfo = GetVideoInfo(args); if (videoInfo == null) return -1; this.Model = Models.FfmpegModel.CreateModel(videoInfo); - this.Init(args); return 1; } } diff --git a/VideoNodes/FfmpegBuilderNodes/Metadata/FfmpegBuilderAutoChapters.cs b/VideoNodes/FfmpegBuilderNodes/Metadata/FfmpegBuilderAutoChapters.cs index a2354274..00a39e26 100644 --- a/VideoNodes/FfmpegBuilderNodes/Metadata/FfmpegBuilderAutoChapters.cs +++ b/VideoNodes/FfmpegBuilderNodes/Metadata/FfmpegBuilderAutoChapters.cs @@ -15,8 +15,6 @@ public override int Execute(NodeParameters args) { - base.Init(args); - VideoInfo videoInfo = GetVideoInfo(args); if (videoInfo == null) return -1; @@ -27,7 +25,7 @@ return 2; } - string tempMetaDataFile = AutoChapters.GenerateMetaDataFile(this, args, videoInfo, ffmpegExe, this.Percent, this.MinimumLength); + string tempMetaDataFile = AutoChapters.GenerateMetaDataFile(this, args, videoInfo, FFMPEG, this.Percent, this.MinimumLength); if (string.IsNullOrEmpty(tempMetaDataFile)) return 2; diff --git a/VideoNodes/FfmpegBuilderNodes/Metadata/FfmpegBuilderComskipChapters.cs b/VideoNodes/FfmpegBuilderNodes/Metadata/FfmpegBuilderComskipChapters.cs index 791eb908..30c0a68e 100644 --- a/VideoNodes/FfmpegBuilderNodes/Metadata/FfmpegBuilderComskipChapters.cs +++ b/VideoNodes/FfmpegBuilderNodes/Metadata/FfmpegBuilderComskipChapters.cs @@ -7,8 +7,6 @@ public class FfmpegBuilderComskipChapters : FfmpegBuilderNode public override int Execute(NodeParameters args) { - base.Init(args); - VideoInfo videoInfo = GetVideoInfo(args); if (videoInfo == null) return -1; diff --git a/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleFormatRemover.cs b/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleFormatRemover.cs index 3e9837a2..4f020c72 100644 --- a/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleFormatRemover.cs +++ b/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleFormatRemover.cs @@ -43,8 +43,6 @@ public class FfmpegBuilderSubtitleFormatRemover : FfmpegBuilderNode public override int Execute(NodeParameters args) { - this.Init(args); - if (RemoveAll) { if (Model.SubtitleStreams.Any() == false) diff --git a/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleTrackMerge.cs b/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleTrackMerge.cs index 065eaf1a..29da64e3 100644 --- a/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleTrackMerge.cs +++ b/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleTrackMerge.cs @@ -42,7 +42,6 @@ public class FfmpegBuilderSubtitleTrackMerge : FfmpegBuilderNode public override int Execute(NodeParameters args) { - this.Init(args); var dir = new FileInfo(UseSourceDirectory ? args.FileName : args.WorkingFile).Directory; if (dir.Exists == false) { diff --git a/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleTrackRemover.cs b/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleTrackRemover.cs index d509e582..7ad67ec5 100644 --- a/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleTrackRemover.cs +++ b/VideoNodes/FfmpegBuilderNodes/Subtitle/FfmpegBuilderSubtitleTrackRemover.cs @@ -20,7 +20,6 @@ public class FfmpegBuilderSubtitleTrackRemover : FfmpegBuilderNode public override int Execute(NodeParameters args) { - this.Init(args); bool removing = false; var regex = new Regex(this.Pattern, RegexOptions.IgnoreCase); foreach(var stream in Model.SubtitleStreams) diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderCropBlackBars.cs b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderCropBlackBars.cs index 4837756b..f63ab692 100644 --- a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderCropBlackBars.cs +++ b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderCropBlackBars.cs @@ -1,41 +1,33 @@ -namespace FileFlows.VideoNodes.FfmpegBuilderNodes +namespace FileFlows.VideoNodes.FfmpegBuilderNodes; +public class FfmpegBuilderCropBlackBars : FfmpegBuilderNode { - public class FfmpegBuilderCropBlackBars : FfmpegBuilderNode + [NumberInt(1)] + [DefaultValue(10)] + public int CroppingThreshold { get; set; } + public override int Outputs => 2; + + public override string HelpUrl => "https://github.com/revenz/FileFlows/wiki/FFMPEG-Builder:-Crop-Black-Bars"; + + public override int Execute(NodeParameters args) { - [NumberInt(1)] - [DefaultValue(10)] - public int CroppingThreshold { get; set; } - public override int Outputs => 2; - - public override string HelpUrl => "https://github.com/revenz/FileFlows/wiki/FFMPEG-Builder:-Crop-Black-Bars"; - - public override int Execute(NodeParameters args) - { - base.Init(args); - - string ffmpeg = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpeg)) - return -1; - - var videoInfo = GetVideoInfo(args); - if (videoInfo == null || videoInfo.VideoStreams?.Any() != true) - return -1; + var videoInfo = GetVideoInfo(args); + if (videoInfo == null || videoInfo.VideoStreams?.Any() != true) + return -1; - string crop = DetectBlackBars.Detect(ffmpeg, videoInfo, args, this.CroppingThreshold); - if (string.IsNullOrWhiteSpace(crop)) - return 2; + string crop = DetectBlackBars.Detect(FFMPEG, videoInfo, args, this.CroppingThreshold); + if (string.IsNullOrWhiteSpace(crop)) + return 2; - //var parts = crop.Split(':'); - ////parts[2] = "iw-" + parts[2]; - ////parts[3] = "ih-" + parts[3]; - //crop = String.Join(":", parts.Take(2)); + //var parts = crop.Split(':'); + ////parts[2] = "iw-" + parts[2]; + ////parts[3] = "ih-" + parts[3]; + //crop = String.Join(":", parts.Take(2)); - args.Logger?.ILog("Black bars detected, crop: " + crop); + args.Logger?.ILog("Black bars detected, crop: " + crop); - var video = Model.VideoStreams[0]; - video.Filter.AddRange(new[] { "crop=" + crop }); - return 1; - } + var video = Model.VideoStreams[0]; + video.Filter.AddRange(new[] { "crop=" + crop }); + return 1; } -} +} \ No newline at end of file diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderHdrToSdr.cs b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderHdrToSdr.cs index 4220e66a..951128ee 100644 --- a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderHdrToSdr.cs +++ b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderHdrToSdr.cs @@ -1,29 +1,26 @@ -namespace FileFlows.VideoNodes.FfmpegBuilderNodes +namespace FileFlows.VideoNodes.FfmpegBuilderNodes; + +public class FfmpegBuilderHdrToSdr : FfmpegBuilderNode { - public class FfmpegBuilderHdrToSdr : FfmpegBuilderNode + public override int Outputs => 2; + + public override string HelpUrl => "https://github.com/revenz/FileFlows/wiki/FFMPEG-Builder:-HDR-to-SDR"; + + public override int Execute(NodeParameters args) { - public override int Outputs => 2; + var videoInfo = GetVideoInfo(args); + if (videoInfo == null || videoInfo.VideoStreams?.Any() != true) + return -1; - public override string HelpUrl => "https://github.com/revenz/FileFlows/wiki/FFMPEG-Builder:-HDR-to-SDR"; - - public override int Execute(NodeParameters args) + var vidStream = Model.VideoStreams?.Where(x => x.Deleted == false && x.Stream?.HDR == true).FirstOrDefault(); + if (vidStream == null) { - base.Init(args); - - var videoInfo = GetVideoInfo(args); - if (videoInfo == null || videoInfo.VideoStreams?.Any() != true) - return -1; - - var vidStream = Model.VideoStreams?.Where(x => x.Deleted == false && x.Stream?.HDR == true).FirstOrDefault(); - if (vidStream == null) - { - args.Logger.ILog("No HDR video stream found"); - return 2; - } - - vidStream.Filter.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p"); - - return 1; + args.Logger.ILog("No HDR video stream found"); + return 2; } + + vidStream.Filter.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p"); + + return 1; } -} +} \ No newline at end of file diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderRemuxToMP4.cs b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderRemuxToMP4.cs index c6891399..7ba285d3 100644 --- a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderRemuxToMP4.cs +++ b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderRemuxToMP4.cs @@ -6,7 +6,6 @@ public class FfmpegBuilderRemuxToMP4: FfmpegBuilderNode public override int Execute(NodeParameters args) { - base.Init(args); this.Model.Extension = "mp4"; return 1; } diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderRemuxToMkv.cs b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderRemuxToMkv.cs index c1995a14..6228feca 100644 --- a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderRemuxToMkv.cs +++ b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderRemuxToMkv.cs @@ -1,12 +1,12 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes; -public class FfmpegBuilderRemuxToMkv: FfmpegBuilderNode +public class FfmpegBuilderRemuxToMkv : FfmpegBuilderNode { public override string HelpUrl => "https://github.com/revenz/FileFlows/wiki/FFMPEG-Builder:-Remux-to-MKV"; + public override int Execute(NodeParameters args) { - base.Init(args); this.Model.Extension = "mkv"; return 1; } -} +} \ No newline at end of file diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderScaler.cs b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderScaler.cs index 74d625b7..9bdefc03 100644 --- a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderScaler.cs +++ b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderScaler.cs @@ -36,12 +36,6 @@ public class FfmpegBuilderScaler : FfmpegBuilderNode public override int Outputs => 2; public override int Execute(NodeParameters args) { - base.Init(args); - - string ffmpeg = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpeg)) - return -1; - var videoInfo = GetVideoInfo(args); if (videoInfo == null || videoInfo.VideoStreams?.Any() != true) return -1; diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideo10Bit.cs b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideo10Bit.cs index 7a58a763..9f18c968 100644 --- a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideo10Bit.cs +++ b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideo10Bit.cs @@ -8,8 +8,6 @@ public override int Execute(NodeParameters args) { - base.Init(args); - var videoInfo = GetVideoInfo(args); if (videoInfo == null || videoInfo.VideoStreams?.Any() != true) return -1; diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoBitrate.cs b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoBitrate.cs index ed50f100..6480536f 100644 --- a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoBitrate.cs +++ b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoBitrate.cs @@ -17,8 +17,6 @@ public class FfmpegBuilderVideoBitrate : FfmpegBuilderNode public override int Execute(NodeParameters args) { - base.Init(args); - var video = Model.VideoStreams?.Where(x => x.Deleted == false)?.FirstOrDefault(); if (video?.Stream == null) { diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoCodec.cs b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoCodec.cs index bb7ee08e..e7d39cda 100644 --- a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoCodec.cs +++ b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoCodec.cs @@ -23,15 +23,13 @@ public override int Execute(NodeParameters args) { - base.Init(args); - string codec = args.ReplaceVariables(VideoCodec ?? string.Empty); string parameters = args.ReplaceVariables(VideoCodecParameters ?? codec); if (string.IsNullOrWhiteSpace(parameters)) return 1; // nothing to do - parameters = CheckVideoCodec(ffmpegExe, parameters); + parameters = CheckVideoCodec(FFMPEG, parameters); bool encoding = false; foreach (var item in Model.VideoStreams.Select((x, index) => (stream: x, index))) diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoEncode.cs b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoEncode.cs new file mode 100644 index 00000000..b7418f1a --- /dev/null +++ b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoEncode.cs @@ -0,0 +1,118 @@ +using FileFlows.VideoNodes.FfmpegBuilderNodes.Models; + +namespace FileFlows.VideoNodes.FfmpegBuilderNodes; + +/// +/// Set a video codec encoding for a video stream based on users settings +/// +public class FfmpegBuilderVideoEncode:FfmpegBuilderNode +{ + public override int Outputs => 1; + + private const string CODEC_H264 = "h264"; + private const string CODEC_H265 = "h265"; + + public override string HelpUrl => "https://github.com/revenz/FileFlows/wiki/FFMPEG-Builder:-Video-Encode"; + + [DefaultValue("h265")] + [Select(nameof(CodecOptions), 1)] + public string Codec { get; set; } + + private static List _CodecOptions; + public static List CodecOptions + { + get + { + if (_CodecOptions == null) + { + _CodecOptions = new List + { + new () { Label = "h264", Value = CODEC_H264 }, + new () { Label = "h265", Value = CODEC_H265 } + }; + } + return _CodecOptions; + } + } + + [DefaultValue(true)] + [Boolean(2)] + public bool HardwareEncoding { get; set; } + + [Slider(3)] + [Range(0, 51)] + [DefaultValue(23)] + public int Quality { get; set; } + + + public override int Execute(NodeParameters args) + { + var stream = Model.VideoStreams.Where(x => x.Deleted == false).First(); + if (Codec == CODEC_H264) + H264(stream); + else if (Codec == CODEC_H264) + H265(stream); + bool encoding = false; + return encoding ? 1 : 2; + } + + private void H264(FfmpegVideoStream stream) + { + if (HardwareEncoding == false) + H26x_CPU(stream); + else if (SupportsHardwareNvidia264()) + H264_Nvidia(stream); + else + H26x_CPU(stream); + } + + private void H265(FfmpegVideoStream stream) + { + // hevc_qsv -load_plugin hevc_hw -pix_fmt p010le -profile:v main10 -global_quality 21 -g 24 -look_ahead 1 -look_ahead_depth 60 + if (HardwareEncoding == false) + H26x_CPU(stream); + else if (SupportsHardwareNvidia265()) + H265_Nvidia(stream); + else + H26x_CPU(stream); + } + + + private void H26x_CPU(FfmpegVideoStream stream) + { + stream.EncodingParameters.Clear(); + stream.EncodingParameters.AddRange(new [] + { + Codec == CODEC_H265 ? "libx265" : "libx264", + "-preset", "slow", + "-crf", Quality.ToString() + }); + } + + private void H264_Nvidia(FfmpegVideoStream stream) + { + stream.EncodingParameters.Clear(); + stream.EncodingParameters.AddRange(new [] + { + "h264_nvenc", + "-rc", "vbr_hq", + // 0 == auto, so we set to 1 + "-cq", Quality <= 0 ? "1" : Quality.ToString(), + }); + } + private void H265_Nvidia(FfmpegVideoStream stream) + { + stream.EncodingParameters.Clear(); + stream.EncodingParameters.AddRange(new [] + { + "hevc_nvenc", + "-rc", "constqp", + "-qp", Quality.ToString(), + //"-b:v", "0K", // this would do a two-pass... slower + "-preset", "p6", + // https://www.reddit.com/r/ffmpeg/comments/gg5szi/what_is_spatial_aq_and_temporal_aq_with_nvenc/ + "-spatial-aq", "1" + }); + } + +} diff --git a/VideoNodes/InputNodes/VideoFile.cs b/VideoNodes/InputNodes/VideoFile.cs index b203a3e9..325f9971 100644 --- a/VideoNodes/InputNodes/VideoFile.cs +++ b/VideoNodes/InputNodes/VideoFile.cs @@ -51,16 +51,12 @@ namespace FileFlows.VideoNodes public override int Execute(NodeParameters args) { - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - VideoInfoHelper.ProbeSize = this.ProbeSize; try { - var videoInfo = new VideoInfoHelper(ffmpegExe, args.Logger).Read(args.WorkingFile); + var videoInfo = new VideoInfoHelper(FFMPEG, args.Logger).Read(args.WorkingFile); if (videoInfo.VideoStreams.Any() == false) { args.Logger.ILog("No video streams detected."); diff --git a/VideoNodes/LogicalNodes/DetectBlackBars.cs b/VideoNodes/LogicalNodes/DetectBlackBars.cs index acb48e30..e6f99653 100644 --- a/VideoNodes/LogicalNodes/DetectBlackBars.cs +++ b/VideoNodes/LogicalNodes/DetectBlackBars.cs @@ -32,16 +32,12 @@ namespace FileFlows.VideoNodes public override int Execute(NodeParameters args) { - string ffmpeg = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpeg)) - return -1; - var videoInfo = GetVideoInfo(args); if (videoInfo == null || videoInfo.VideoStreams?.Any() != true) return -1; - string crop = Detect(ffmpeg, videoInfo, args, this.CroppingThreshold); + string crop = Detect(FFMPEG, videoInfo, args, this.CroppingThreshold); if (crop == string.Empty) return 2; diff --git a/VideoNodes/VideoNodes.csproj b/VideoNodes/VideoNodes.csproj index 4d5d4ac95c6bcb413a41d13a8a6373d091fd7762..1b68acfdd3a977a265f456de0fe7d4381b26de9f 100644 GIT binary patch literal 1999 zcmd5--*4MC5PpvT3PK3r1n4N+Yn@<`S$Iypb!a^!cDk1V10$VmHWVq4R8j-|<9j6K zA8`T{1NzVxN%!&N-SO`DDSP{+lH>u56eGn*zpCPPB5M+*F$d$nxD6XL#Tk-*!2Qak}wq*F>Za1$H1QQDOQyA=#cj5;5DB35ROEwSJC9|1@6&G z=fqbxrm-}ls-Sq9!xjw0iLPp{Hp~Z{O8scFbPI*`apTt7SoW7*D>7Dv0Z-YYF;>is zeG&y|GSdYhqfLrM2G8&L3J~*iacd-7J6GG^(zL{|r4LJUU8T6~88bMz2AdmEJDfWf z0+3Q0QX8Fv_1z~AA-4BsDIv?Xu%wpFN+^ zEZVYo61{<1BJV6A$t#llmzuzg)zWohd%y7$hpZy(?RB*S@sV*mIf-hc#d;io_3N%r1B6(sSn zh2&WeyJR#Xv2(sumw1nYtflU7Pl$S-KmYkB%TM~kr8KgI>G z9hhYAWIzY>@_+f*>HN!HP;R%ih<}U&GmI!swO8u>L4yDVd7z-^XRB2^&3HW~y521H z-;JvqcP$LckTx4OmXCZBB=4owII5_JAr2!X4CX-^-aarxRxdPQ9+YUG%b!BIId^MD x>A!nDh%8!2nWZ~MNCBfPYRE+?@~!+&xwQC0I)>YfcRBdW{IrfiJG=_B&NowniMap( literal 4098 zcmeH~TW=Fr6ovOImG}>%2q_@JI2THyHW-CVN>B+FG4!Pp8e%7LaqP&KhDiMOmT&Fj zleyR)0+hGPvS-F;pLO|Z=$nS9=JU_ z*5K57dFhd(?}K&@N3x&?&KqY*JdD8>H<`U>y^GEbtCub#AJ~+A{j2@+X!8gEO>>i+}G!q|U;gF|3yg$R=6K6;sYWn^3N^^Pn+p(nC4fosB`RKvVDJO@R z5kGs}>smgyh4$jFPQGk)#CZ!=9_amqY2VXx=$I0WecOvPr9D2_9&BsWUM7`(4~)tr|3Ju z=CS=kt~H405K+fhHm6{Smxf#40_{tfMID~Hrm9puPMPqlTxu4qNH|k`C9JF39r+H` zO;rTd(cJc^-I#b(9JJA2QLeqpvlHKyxW>xW%%8PB06DOh@V|JJRfAcTR&8B;PLx)q zKIWtR2z4Opea0yhbO;(n@5FtICdERR6BHS`7Eh9u!Tx{~_z||aI72$)8|_Zr2g^7u z^8T9LLry$kzp`QsL-L$zMY15WQ@dri?IqRaF+3^;I+citNU6OBMSKUm+uqSu>@K(~ z_KxozKC0YxI!M=$$&J}L%llF;8)A5oWMaxo<(N83-g8&ouCOK+Pq0q?EXv%mPcG4N zd(?pyi)UD{N}qkmuh@R?vGvKb_>z-a^sH4Z4IMV`Z1VmeppVd`NhjZ;g09=Iv%3xM zp1|-nHFcfe{3%GqwKZyOac#5obieRaM%l2RuhzCWqx`hRn!HlhF>)Jx5p@r__dAwr zicy_d^S#U0BaTj3?xpKbBtOodieB~1%(L=;pZ<3~)vU5lW_8J*e70QX$%G72&8f@H zRceH?P_lU*=9zb6HXjvxlKh+3X;r)gt%{SFX7RfDxy_z2cI1jE`zfn2(@p zl?@Ri9QF7`-p%Kw*pJnpC7<#(r8lO*T=fnWP}t{8e{HU>sQQ|X^!t?Fq0X;aRvkj~ zq23NI@XaNNvh8d3#(EihVA&@1=%n!GrnjK)=k0o#p-vblscMG{+CGO1F|S!rGffj# zQ~Pt_R8d4riDbQ ffArgs = new List { "-c", "copy", @@ -145,7 +141,7 @@ if(extension.StartsWith(".")) extension = extension.Substring(1); - if (Encode(args, ffmpegExe, ffArgs, extension) == false) + if (Encode(args, FFMPEG, ffArgs, extension) == false) return -1; return 1; diff --git a/VideoNodes/VideoNodes/AudioAdjustVolume.cs b/VideoNodes/VideoNodes/AudioAdjustVolume.cs index 7200b5a0..4567ec32 100644 --- a/VideoNodes/VideoNodes/AudioAdjustVolume.cs +++ b/VideoNodes/VideoNodes/AudioAdjustVolume.cs @@ -26,11 +26,7 @@ VideoInfo videoInfo = GetVideoInfo(args); if (videoInfo == null) return -1; - - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - + if (videoInfo.AudioStreams?.Any() != true) { args.Logger?.ILog("No audio streams detected"); @@ -39,7 +35,7 @@ if(VolumePercent == 100) { - args.Logger?.ILog("Volume percent set to 100, no adjustment necassary"); + args.Logger?.ILog("Volume percent set to 100, no adjustment necessary"); return 2; } @@ -63,7 +59,7 @@ if(extension.StartsWith(".")) extension = extension.Substring(1); - if (Encode(args, ffmpegExe, ffArgs, extension) == false) + if (Encode(args, FFMPEG, ffArgs, extension) == false) return -1; return 1; diff --git a/VideoNodes/VideoNodes/AudioNormalization.cs b/VideoNodes/VideoNodes/AudioNormalization.cs index 62c6ae3d..a99bae72 100644 --- a/VideoNodes/VideoNodes/AudioNormalization.cs +++ b/VideoNodes/VideoNodes/AudioNormalization.cs @@ -39,10 +39,6 @@ public class AudioNormalization: EncodingNode if (videoInfo == null) return -1; - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - if (videoInfo.AudioStreams?.Any() != true) { args.Logger?.ILog("No audio streams detected"); @@ -82,7 +78,7 @@ public class AudioNormalization: EncodingNode { if (TwoPass) { - string twoPass = DoTwoPass(this, args, ffmpegExe, j); + string twoPass = DoTwoPass(this, args, FFMPEG, j); ffArgs.AddRange(new[] { "-map", $"0:a:{j}", "-c:a:" + j, audio.Codec, "-filter:a:" + j, twoPass }); } else @@ -113,7 +109,7 @@ public class AudioNormalization: EncodingNode if (extension.StartsWith(".")) extension = extension.Substring(1); - if (Encode(args, ffmpegExe, ffArgs, extension) == false) + if (Encode(args, FFMPEG, ffArgs, extension) == false) return -1; return 1; diff --git a/VideoNodes/VideoNodes/AudioTrackRemover.cs b/VideoNodes/VideoNodes/AudioTrackRemover.cs index d6156ed6..9a634e64 100644 --- a/VideoNodes/VideoNodes/AudioTrackRemover.cs +++ b/VideoNodes/VideoNodes/AudioTrackRemover.cs @@ -30,10 +30,6 @@ if (videoInfo == null) return -1; - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - List ffArgs = new List { "-c", "copy", @@ -74,7 +70,7 @@ if(extension.StartsWith(".")) extension = extension.Substring(1); - if (Encode(args, ffmpegExe, ffArgs, extension) == false) + if (Encode(args, FFMPEG, ffArgs, extension) == false) return -1; return 1; diff --git a/VideoNodes/VideoNodes/AudioTrackReorder.cs b/VideoNodes/VideoNodes/AudioTrackReorder.cs index 7c27ca0f..fc1498ee 100644 --- a/VideoNodes/VideoNodes/AudioTrackReorder.cs +++ b/VideoNodes/VideoNodes/AudioTrackReorder.cs @@ -92,10 +92,6 @@ if (videoInfo == null) return -1; - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - List ffArgs = new List { "-c", "copy", @@ -130,7 +126,7 @@ if(extension.StartsWith(".")) extension = extension.Substring(1); - if (Encode(args, ffmpegExe, ffArgs, extension) == false) + if (Encode(args, FFMPEG, ffArgs, extension) == false) return -1; return 1; diff --git a/VideoNodes/VideoNodes/AudioTrackSetLanguage.cs b/VideoNodes/VideoNodes/AudioTrackSetLanguage.cs index 71f20f80..039df493 100644 --- a/VideoNodes/VideoNodes/AudioTrackSetLanguage.cs +++ b/VideoNodes/VideoNodes/AudioTrackSetLanguage.cs @@ -24,10 +24,6 @@ if (videoInfo == null) return -1; - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - List ffArgs = new List(); int index = 0; @@ -54,7 +50,7 @@ args.Logger?.DLog("Working file: " + args.WorkingFile); args.Logger?.DLog("Extension: " + extension); - if (Encode(args, ffmpegExe, ffArgs, extension) == false) + if (Encode(args, FFMPEG, ffArgs, extension) == false) return -1; return 1; diff --git a/VideoNodes/VideoNodes/AutoChapters.cs b/VideoNodes/VideoNodes/AutoChapters.cs index 2107feee..8bfcc5f3 100644 --- a/VideoNodes/VideoNodes/AutoChapters.cs +++ b/VideoNodes/VideoNodes/AutoChapters.cs @@ -25,9 +25,6 @@ public override int Execute(NodeParameters args) { - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; VideoInfo videoInfo = GetVideoInfo(args); if (videoInfo == null) return -1; @@ -38,12 +35,12 @@ return 2; } - string tempMetaDataFile = GenerateMetaDataFile(this, args, videoInfo, ffmpegExe, this.Percent, this.MinimumLength); + string tempMetaDataFile = GenerateMetaDataFile(this, args, videoInfo, FFMPEG, this.Percent, this.MinimumLength); if (string.IsNullOrEmpty(tempMetaDataFile)) return 2; string[] ffArgs = new[] { "-i", tempMetaDataFile, "-map_metadata", "1", "-codec", "copy", "-max_muxing_queue_size", "1024" }; - if (Encode(args, ffmpegExe, ffArgs.ToList())) + if (Encode(args, FFMPEG, ffArgs.ToList())) { args.Logger?.ILog($"Adding chapters to file"); return 1; diff --git a/VideoNodes/VideoNodes/ComskipChapters.cs b/VideoNodes/VideoNodes/ComskipChapters.cs index d3a21431..98e64ad1 100644 --- a/VideoNodes/VideoNodes/ComskipChapters.cs +++ b/VideoNodes/VideoNodes/ComskipChapters.cs @@ -15,9 +15,6 @@ public override int Execute(NodeParameters args) { - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; VideoInfo videoInfo = GetVideoInfo(args); if (videoInfo == null) return -1; @@ -27,7 +24,7 @@ return 2; string[] ffArgs = new[] { "-i", tempMetaDataFile, "-map_metadata", "1", "-codec", "copy", "-max_muxing_queue_size", "1024" }; - if (Encode(args, ffmpegExe, ffArgs.ToList())) + if (Encode(args, FFMPEG, ffArgs.ToList())) { args.Logger?.ILog($"Added chapters to file"); return 1; diff --git a/VideoNodes/VideoNodes/ComskipRemoveAds.cs b/VideoNodes/VideoNodes/ComskipRemoveAds.cs index 4e49141c..833f4300 100644 --- a/VideoNodes/VideoNodes/ComskipRemoveAds.cs +++ b/VideoNodes/VideoNodes/ComskipRemoveAds.cs @@ -15,9 +15,6 @@ public override int Execute(NodeParameters args) { - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; VideoInfo videoInfo = GetVideoInfo(args); if (videoInfo == null) return -1; @@ -109,7 +106,7 @@ "-c", "copy" }; - bool concatResult = Encode(args, ffmpegExe, ffArgs, dontAddInputFile: true, extension: extension); + bool concatResult = Encode(args, FFMPEG, ffArgs, dontAddInputFile: true, extension: extension); foreach(string segment in segments.Union(new[] { concatList })) { @@ -140,7 +137,7 @@ "-t", duration.ToString(), "-c", "copy" }; - if (Encode(args, ffmpegExe, ffArgs, outputFile: segment, updateWorkingFile: false)) + if (Encode(args, FFMPEG, ffArgs, outputFile: segment, updateWorkingFile: false)) { segments.Add(segment); segmentsInfo.Add(DebugString(start, end)); diff --git a/VideoNodes/VideoNodes/EncodingNode.cs b/VideoNodes/VideoNodes/EncodingNode.cs index 3230aa58..1e6701e3 100644 --- a/VideoNodes/VideoNodes/EncodingNode.cs +++ b/VideoNodes/VideoNodes/EncodingNode.cs @@ -14,8 +14,6 @@ namespace FileFlows.VideoNodes protected TimeSpan TotalTime; - protected NodeParameters args; - private FFMpegEncoder Encoder; public bool Encode(NodeParameters args, string ffmpegExe, List ffmpegParameters, string extension = "mkv", string outputFile = "", bool updateWorkingFile = true, bool dontAddInputFile = false, bool dontAddOutputFile = false) @@ -29,7 +27,6 @@ namespace FileFlows.VideoNodes if (string.IsNullOrEmpty(extension)) extension = "mkv"; - this.args = args; Encoder = new FFMpegEncoder(ffmpegExe, args.Logger); Encoder.AtTime += AtTimeEvent; @@ -73,26 +70,14 @@ namespace FileFlows.VideoNodes { if (TotalTime.TotalMilliseconds == 0) { - args?.Logger?.DLog("Can't report time progress as total time is 0"); + Args?.Logger?.DLog("Can't report time progress as total time is 0"); return; } float percent = (float)((time.TotalMilliseconds / TotalTime.TotalMilliseconds) * 100); - if(args?.PartPercentageUpdate != null) - args.PartPercentageUpdate(percent); + if(Args?.PartPercentageUpdate != null) + Args.PartPercentageUpdate(percent); } - -#if (DEBUG) - /// - /// Used for unit tests - /// - /// the args - public void SetArgs(NodeParameters args) - { - this.args = args; - } -#endif - public string CheckVideoCodec(string ffmpeg, string vidparams) { if (string.IsNullOrEmpty(vidparams)) @@ -128,7 +113,7 @@ namespace FileFlows.VideoNodes if (canProcess == false) { // change to cpu encoding - args.Logger?.ILog("Can't encode using hevc_nvenc, falling back to CPU encoding H265 (libx265)"); + Args.Logger?.ILog("Can't encode using hevc_nvenc, falling back to CPU encoding H265 (libx265)"); return "libx265"; } return vidparams; @@ -140,7 +125,7 @@ namespace FileFlows.VideoNodes if (canProcess == false) { // change to cpu encoding - args.Logger?.ILog("Can't encode using h264_nvenc, falling back to CPU encoding H264 (libx264)"); + Args.Logger?.ILog("Can't encode using h264_nvenc, falling back to CPU encoding H264 (libx264)"); return "libx264"; } return vidparams; @@ -152,7 +137,7 @@ namespace FileFlows.VideoNodes if (canProcess == false) { // change to cpu encoding - args.Logger?.ILog("Can't encode using hevc_qsv, falling back to CPU encoding H265 (libx265)"); + Args.Logger?.ILog("Can't encode using hevc_qsv, falling back to CPU encoding H265 (libx265)"); return "libx265"; } return vidparams; @@ -164,7 +149,7 @@ namespace FileFlows.VideoNodes if (canProcess == false) { // change to cpu encoding - args.Logger?.ILog("Can't encode using h264_qsv, falling back to CPU encoding H264 (libx264)"); + Args.Logger?.ILog("Can't encode using h264_qsv, falling back to CPU encoding H264 (libx264)"); return "libx264"; } return vidparams; @@ -177,7 +162,7 @@ namespace FileFlows.VideoNodes //ffmpeg -loglevel error -f lavfi -i color=black:s=1080x1080 -vframes 1 -an -c:v hevc_nven2c -preset hq -f null -" string cmdArgs = $"-loglevel error -f lavfi -i color=black:s=1080x1080 -vframes 1 -an -c:v {encodingParams} -f null -\""; - var cmd = args.Process.ExecuteShellCommand(new ExecuteArgs + var cmd = Args.Process.ExecuteShellCommand(new ExecuteArgs { Command = ffmpeg, Arguments = cmdArgs, @@ -185,7 +170,7 @@ namespace FileFlows.VideoNodes }).Result; if (cmd.ExitCode != 0 || string.IsNullOrWhiteSpace(cmd.Output) == false) { - args.Logger?.WLog($"Cant process '{encodingParams}': {cmd.Output ?? ""}"); + Args.Logger?.WLog($"Cant process '{encodingParams}': {cmd.Output ?? ""}"); return false; } return true; @@ -197,7 +182,7 @@ namespace FileFlows.VideoNodes { if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) { - var cmd = args.Process.ExecuteShellCommand(new ExecuteArgs + var cmd = Args.Process.ExecuteShellCommand(new ExecuteArgs { Command = "wmic", Arguments = "path win32_VideoController get name" @@ -222,7 +207,7 @@ namespace FileFlows.VideoNodes } // check cuda in ffmpeg itself - var result = args.Process.ExecuteShellCommand(new ExecuteArgs + var result = Args.Process.ExecuteShellCommand(new ExecuteArgs { Command = ffmpeg, Arguments = "-hide_banner -init_hw_device list" @@ -231,7 +216,7 @@ namespace FileFlows.VideoNodes } catch (Exception ex) { - args.Logger?.ELog("Failed to detect NVIDIA card: " + ex.Message + Environment.NewLine + ex.StackTrace); + Args.Logger?.ELog("Failed to detect NVIDIA card: " + ex.Message + Environment.NewLine + ex.StackTrace); return false; } } diff --git a/VideoNodes/VideoNodes/FFMPEG.cs b/VideoNodes/VideoNodes/FFMPEG.cs index 972de0ff..a31f6101 100644 --- a/VideoNodes/VideoNodes/FFMPEG.cs +++ b/VideoNodes/VideoNodes/FFMPEG.cs @@ -41,12 +41,8 @@ namespace FileFlows.VideoNodes args.Logger.ELog("Command Line not set"); return -1; } - this.args = args; try { - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; if (string.IsNullOrEmpty(Extension)) Extension = "mkv"; @@ -54,7 +50,7 @@ namespace FileFlows.VideoNodes string outputFile = Path.Combine(args.TempPath, Guid.NewGuid().ToString() + "." + Extension); var ffArgs = GetFFMPEGArgs(args, outputFile); - if (Encode(args, ffmpegExe, ffArgs, updateWorkingFile: false, dontAddInputFile: true, dontAddOutputFile: true) == false) + if (Encode(args, FFMPEG, ffArgs, updateWorkingFile: false, dontAddInputFile: true, dontAddOutputFile: true) == false) return -1; if (File.Exists(outputFile)) diff --git a/VideoNodes/VideoNodes/ReadVideoInfo.cs b/VideoNodes/VideoNodes/ReadVideoInfo.cs index 25063925..3e45edf4 100644 --- a/VideoNodes/VideoNodes/ReadVideoInfo.cs +++ b/VideoNodes/VideoNodes/ReadVideoInfo.cs @@ -42,14 +42,10 @@ namespace FileFlows.VideoNodes public override int Execute(NodeParameters args) { - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - try { - var videoInfo = new VideoInfoHelper(ffmpegExe, args.Logger).Read(args.WorkingFile); + var videoInfo = new VideoInfoHelper(FFMPEG, args.Logger).Read(args.WorkingFile); if (videoInfo.VideoStreams.Any() == false) { args.Logger.ILog("No video streams detected."); diff --git a/VideoNodes/VideoNodes/Remux.cs b/VideoNodes/VideoNodes/Remux.cs index 69dfb7d9..32d368cb 100644 --- a/VideoNodes/VideoNodes/Remux.cs +++ b/VideoNodes/VideoNodes/Remux.cs @@ -13,17 +13,12 @@ public override int Execute(NodeParameters args) { - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - - if (Force == false && args.WorkingFile?.ToLower()?.EndsWith(".mkv") == true) return 2; try { - if (Encode(args, ffmpegExe, new List { "-c", "copy", "-map", "0" }, "mkv") == false) + if (Encode(args, FFMPEG, new List { "-c", "copy", "-map", "0" }, "mkv") == false) return -1; return 1; @@ -45,16 +40,12 @@ public override int Execute(NodeParameters args) { - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - if (Force == false && args.WorkingFile?.ToLower()?.EndsWith(".mp4") == true) return 2; try { - if (Encode(args, ffmpegExe, new List { "-c", "copy", "-map", "0" }, "mp4") == false) + if (Encode(args, FFMPEG, new List { "-c", "copy", "-map", "0" }, "mp4") == false) return -1; return 1; diff --git a/VideoNodes/VideoNodes/SubtitleExtractor.cs b/VideoNodes/VideoNodes/SubtitleExtractor.cs index 43d594d1..7ebde434 100644 --- a/VideoNodes/VideoNodes/SubtitleExtractor.cs +++ b/VideoNodes/VideoNodes/SubtitleExtractor.cs @@ -38,11 +38,7 @@ VideoInfo videoInfo = GetVideoInfo(args); if (videoInfo == null) return -1; - - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - + // ffmpeg -i input.mkv -map "0:m:language:eng" -map "-0:v" -map "-0:a" output.srt var subTrack = videoInfo.SubtitleStreams?.Where(x => string.IsNullOrEmpty(Language) || x.Language?.ToLower() == Language.ToLower()).FirstOrDefault(); if (subTrack == null) @@ -74,7 +70,7 @@ //bool textSubtitles = Regex.IsMatch(OutputFile, @"\.(sup)$") == false; - var extracted = ExtractSubtitle(args, ffmpegExe, "0:s:" + subTrack.TypeIndex, OutputFile); + var extracted = ExtractSubtitle(args, FFMPEG, "0:s:" + subTrack.TypeIndex, OutputFile); if(extracted) { args.UpdateVariables(new Dictionary diff --git a/VideoNodes/VideoNodes/SubtitleLanguageRemover.cs b/VideoNodes/VideoNodes/SubtitleLanguageRemover.cs index bd7c210c..14385b71 100644 --- a/VideoNodes/VideoNodes/SubtitleLanguageRemover.cs +++ b/VideoNodes/VideoNodes/SubtitleLanguageRemover.cs @@ -32,10 +32,6 @@ if (videoInfo == null) return -1; - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - List ffArgs = new List() { "-map", "0:v", @@ -74,7 +70,7 @@ if(extension.StartsWith(".")) extension = extension.Substring(1); - if (Encode(args, ffmpegExe, ffArgs, extension) == false) + if (Encode(args, FFMPEG, ffArgs, extension) == false) return -1; return 1; diff --git a/VideoNodes/VideoNodes/SubtitleRemover.cs b/VideoNodes/VideoNodes/SubtitleRemover.cs index 4b5fadc0..331ed7a8 100644 --- a/VideoNodes/VideoNodes/SubtitleRemover.cs +++ b/VideoNodes/VideoNodes/SubtitleRemover.cs @@ -57,10 +57,6 @@ if (videoInfo == null) return -1; - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - List ffArgs = new List() { "-map", "0:v", @@ -106,7 +102,7 @@ if(extension.StartsWith(".")) extension = extension.Substring(1); - if (Encode(args, ffmpegExe, ffArgs, extension) == false) + if (Encode(args, FFMPEG, ffArgs, extension) == false) return -1; return 1; diff --git a/VideoNodes/VideoNodes/VideoEncode.cs b/VideoNodes/VideoNodes/VideoEncode.cs index 98da93cf..37f89fc0 100644 --- a/VideoNodes/VideoNodes/VideoEncode.cs +++ b/VideoNodes/VideoNodes/VideoEncode.cs @@ -56,7 +56,6 @@ namespace FileFlows.VideoNodes VideoCodec = VideoCodec.ToLower(); AudioCodec = AudioCodec.ToLower(); - this.args = args; try { VideoInfo videoInfo = GetVideoInfo(args); @@ -65,10 +64,6 @@ namespace FileFlows.VideoNodes Language = Language?.ToLower() ?? ""; - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - // ffmpeg is one based for stream index, so video should be 1, audio should be 2 string encodeVideoParameters = string.Empty, encodeAudioParameters = string.Empty; @@ -88,7 +83,7 @@ namespace FileFlows.VideoNodes if (videoIsRightCodec == null || crop != string.Empty) { - string codecParameters = CheckVideoCodec(ffmpegExe, VideoCodecParameters); + string codecParameters = CheckVideoCodec(FFMPEG, VideoCodecParameters); encodeVideoParameters = $"-map 0:v:0 -c:v {codecParameters} {crop}"; } Extension = args.ReplaceVariables(Extension)?.EmptyAsNull() ?? "mkv"; @@ -162,7 +157,7 @@ namespace FileFlows.VideoNodes } - if (Encode(args, ffmpegExe, ffArgs, Extension) == false) + if (Encode(args, FFMPEG, ffArgs, Extension) == false) return -1; return 1; diff --git a/VideoNodes/VideoNodes/VideoNode.cs b/VideoNodes/VideoNodes/VideoNode.cs index 750af1f1..a33bfdcf 100644 --- a/VideoNodes/VideoNodes/VideoNode.cs +++ b/VideoNodes/VideoNodes/VideoNode.cs @@ -4,64 +4,74 @@ namespace FileFlows.VideoNodes public abstract class VideoNode : Node { + /// + /// Gets the Node Parameters + /// + protected NodeParameters Args { get; private set; } + + + +#if (DEBUG) + /// + /// Used for unit tests + /// + /// the args + public void SetArgs(NodeParameters args) + { + this.Args = args; + } +#endif + + /// + /// Gets the FFMPEG executable location + /// + protected string FFMPEG { get; private set; } public override string Icon => "fas fa-video"; - protected string GetFFMpegExe(NodeParameters args) + /// + /// Executed before execute, sets ffmpegexe etc + /// + /// the node parametes + /// true if successfully + public override bool PreExecute(NodeParameters args) { - string ffmpeg = args.GetToolPath("FFMpeg"); + this.Args = args; + this.FFMPEG = GetFFMpegExe(); + return string.IsNullOrEmpty(this.FFMPEG) == false; + } + + private string GetFFMpegExe() + { + string ffmpeg = Args.GetToolPath("FFMpeg"); if (string.IsNullOrEmpty(ffmpeg)) { - args.Logger.ELog("FFMpeg tool not found."); + Args.Logger.ELog("FFMpeg tool not found."); return ""; } var fileInfo = new FileInfo(ffmpeg); if (fileInfo.Exists == false) { - args.Logger.ELog("FFMpeg tool configured by ffmpeg.exe file does not exist."); + Args.Logger.ELog("FFMpeg tool configured by ffmpeg.exe file does not exist."); return ""; } return fileInfo.FullName; } - protected string GetFFMpegPath(NodeParameters args) - { - string ffmpeg = args.GetToolPath("FFMpeg"); - if (string.IsNullOrEmpty(ffmpeg)) - { - args.Logger.ELog("FFMpeg tool not found."); - return ""; - } - var fileInfo = new FileInfo(ffmpeg); - if (fileInfo.Exists == false) - { - args.Logger.ELog("FFMpeg tool configured by ffmpeg.exe file does not exist."); - return ""; - } - return fileInfo.DirectoryName; - } - protected string GetFFPlayExe(NodeParameters args) - { - string ffmpeg = args.GetToolPath("FFMpeg"); - if (string.IsNullOrEmpty(ffmpeg)) - { - args.Logger.ELog("FFMpeg tool not found."); - return ""; - } - - var fileInfo = new FileInfo(ffmpeg); - if (fileInfo.Exists == false) - { - args.Logger.ELog("FFMpeg tool configured by ffmpeg file does not exist."); - return ""; - } - - var ffplay = Path.Combine(fileInfo.DirectoryName, "ffplay" + fileInfo.Extension); - if (File.Exists(ffplay) == false) - { - args.Logger.ELog("FFMpeg tool configured by ffplay file does not exist."); - return ""; - } - return ffplay; - } + // protected string GetFFMpegPath(NodeParameters args) + // { + // string ffmpeg = args.GetToolPath("FFMpeg"); + // if (string.IsNullOrEmpty(ffmpeg)) + // { + // args.Logger.ELog("FFMpeg tool not found."); + // return ""; + // } + // var fileInfo = new FileInfo(ffmpeg); + // if (fileInfo.Exists == false) + // { + // args.Logger.ELog("FFMpeg tool configured by ffmpeg.exe file does not exist."); + // return ""; + // } + // return fileInfo.DirectoryName; + // } private const string VIDEO_INFO = "VideoInfo"; protected void SetVideoInfo(NodeParameters args, VideoInfo videoInfo, Dictionary variables) @@ -119,5 +129,76 @@ namespace FileFlows.VideoNodes } return result; } + + + + + + private bool? HW_NVIDIA_265; + /// + /// Can process NVIDIA h265 hardware encoding + /// + /// true if can support NVIDIA h265 hardware encoding + protected bool SupportsHardwareNvidia265() + { + if (HW_NVIDIA_265 == null) + HW_NVIDIA_265 = CanProcessEncoder("hevc_nvenc"); + return HW_NVIDIA_265.Value; + } + + private bool? HW_NVIDIA_264; + /// + /// Can process NVIDIA h264 hardware encoding + /// + /// true if can support NVIDIA h264 hardware encoding + protected bool SupportsHardwareNvidia264() + { + if (HW_NVIDIA_264 == null) + HW_NVIDIA_264 = CanProcessEncoder("h264_nvenc"); + return HW_NVIDIA_264.Value; + } + + + private bool? HW_QSV_265; + /// + /// Can process QSV h265 hardware encoding + /// + /// true if can support QSV h265 hardware encoding + protected bool SupportsHardwareQsv265() + { + if (HW_QSV_265 == null) + HW_QSV_265 = CanProcessEncoder("hevc_qsv"); + return HW_QSV_265.Value; + } + private bool? HW_QSV_264; + /// + /// Can process QSV h264 hardware encoding + /// + /// true if can support QSV h264 hardware encoding + protected bool SupportsHardwareQsv264() + { + if (HW_QSV_264 == null) + HW_QSV_264 = CanProcessEncoder("h264_qsv"); + return HW_QSV_264.Value; + } + + public bool CanProcessEncoder(string encodingParams) + { + //ffmpeg -loglevel error -f lavfi -i color=black:s=1080x1080 -vframes 1 -an -c:v hevc_nven2c -preset hq -f null -" + + string cmdArgs = $"-loglevel error -f lavfi -i color=black:s=1080x1080 -vframes 1 -an -c:v {encodingParams} -f null -\""; + var cmd = Args.Process.ExecuteShellCommand(new ExecuteArgs + { + Command = FFMPEG, + Arguments = cmdArgs, + Silent = true + }).Result; + if (cmd.ExitCode != 0 || string.IsNullOrWhiteSpace(cmd.Output) == false) + { + Args.Logger?.WLog($"Cant process '{encodingParams}': {cmd.Output ?? ""}"); + return false; + } + return true; + } } } \ No newline at end of file diff --git a/VideoNodes/VideoNodes/VideoScaler.cs b/VideoNodes/VideoNodes/VideoScaler.cs index 4c604fe6..c159f33a 100644 --- a/VideoNodes/VideoNodes/VideoScaler.cs +++ b/VideoNodes/VideoNodes/VideoScaler.cs @@ -89,7 +89,6 @@ namespace FileFlows.VideoNodes public override int Execute(NodeParameters args) { - this.args = args; Extension = args.ReplaceVariables(Extension)?.EmptyAsNull() ?? "mkv"; try @@ -111,12 +110,7 @@ namespace FileFlows.VideoNodes else if (resolution == ResolutionHelper.Resolution.r480p && Resolution.StartsWith("640")) return 2; } - - - string ffmpegExe = GetFFMpegExe(args); - if (string.IsNullOrEmpty(ffmpegExe)) - return -1; - + List ffArgs = new List() { "-vf", $"scale={Resolution}:flags=lanczos", @@ -124,7 +118,7 @@ namespace FileFlows.VideoNodes }; string codec = VideoCodec == "Custom" && string.IsNullOrWhiteSpace(VideoCodecParameters) == false ? - VideoCodecParameters : CheckVideoCodec(ffmpegExe, VideoCodec); + VideoCodecParameters : CheckVideoCodec(FFMPEG, VideoCodec); foreach (string c in codec.Split(" ")) { @@ -133,7 +127,7 @@ namespace FileFlows.VideoNodes ffArgs.Add(c.Trim()); } - if (Encode(args, ffmpegExe, ffArgs, Extension) == false) + if (Encode(args, FFMPEG, ffArgs, Extension) == false) return -1; return 1;