From 73c695919c6569bfb667c36fc5a6b9b862130a0d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 26 Feb 2018 10:48:53 +0100 Subject: [PATCH] Add 30 fps limit in transcoding --- CHANGELOG.md | 13 +++++ server/helpers/ffmpeg-utils.ts | 53 ++++++++++++++---- server/initializers/constants.ts | 2 + .../tests/api/fixtures/video_60fps_short.mp4 | Bin 0 -> 33968 bytes server/tests/api/videos/video-transcoder.ts | 33 ++++++++++- 5 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 server/tests/api/fixtures/video_60fps_short.mp4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f919fef2..d780c2396 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## v0.0.28-alpha + +### BREAKING CHANGES + + * Enable original file transcoding by default in configuration + * Disable transcoding in other definitions in configuration + +### Features + + * Fallback to HTTP if video cannot be loaded + * Limit to 30 FPS in transcoding + + ## v0.0.27-alpha ### Features diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index c2581f460..ad6f2f867 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -1,16 +1,27 @@ import * as ffmpeg from 'fluent-ffmpeg' import { VideoResolution } from '../../shared/models/videos' -import { CONFIG } from '../initializers' +import { CONFIG, MAX_VIDEO_TRANSCODING_FPS } from '../initializers' -function getVideoFileHeight (path: string) { - return new Promise((res, rej) => { - ffmpeg.ffprobe(path, (err, metadata) => { - if (err) return rej(err) +async function getVideoFileHeight (path: string) { + const videoStream = await getVideoFileStream(path) + return videoStream.height +} - const videoStream = metadata.streams.find(s => s.codec_type === 'video') - return res(videoStream.height) - }) - }) +async function getVideoFileFPS (path: string) { + const videoStream = await getVideoFileStream(path) + + for (const key of [ 'r_frame_rate' , 'avg_frame_rate' ]) { + const valuesText: string = videoStream[key] + if (!valuesText) continue + + const [ frames, seconds ] = valuesText.split('/') + if (!frames || !seconds) continue + + const result = parseInt(frames, 10) / parseInt(seconds, 10) + if (result > 0) return result + } + + return 0 } function getDurationFromVideoFile (path: string) { @@ -49,7 +60,9 @@ type TranscodeOptions = { } function transcode (options: TranscodeOptions) { - return new Promise((res, rej) => { + return new Promise(async (res, rej) => { + const fps = await getVideoFileFPS(options.inputPath) + let command = ffmpeg(options.inputPath) .output(options.outputPath) .videoCodec('libx264') @@ -57,6 +70,8 @@ function transcode (options: TranscodeOptions) { .outputOption('-movflags faststart') // .outputOption('-crf 18') + if (fps > MAX_VIDEO_TRANSCODING_FPS) command = command.withFPS(MAX_VIDEO_TRANSCODING_FPS) + if (options.resolution !== undefined) { const size = `?x${options.resolution}` // '?x720' for example command = command.size(size) @@ -74,5 +89,21 @@ export { getVideoFileHeight, getDurationFromVideoFile, generateImageFromVideoFile, - transcode + transcode, + getVideoFileFPS +} + +// --------------------------------------------------------------------------- + +function getVideoFileStream (path: string) { + return new Promise((res, rej) => { + ffmpeg.ffprobe(path, (err, metadata) => { + if (err) return rej(err) + + const videoStream = metadata.streams.find(s => s.codec_type === 'video') + if (!videoStream) throw new Error('Cannot find video stream of ' + path) + + return res(videoStream) + }) + }) } diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 2dc73770d..318df48bf 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -232,6 +232,7 @@ const CONSTRAINTS_FIELDS = { } let VIDEO_VIEW_LIFETIME = 60000 * 60 // 1 hour +const MAX_VIDEO_TRANSCODING_FPS = 30 const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = { LIKE: 'like', @@ -442,6 +443,7 @@ export { VIDEO_LICENCES, VIDEO_RATE_TYPES, VIDEO_MIMETYPE_EXT, + MAX_VIDEO_TRANSCODING_FPS, USER_PASSWORD_RESET_LIFETIME, IMAGE_MIMETYPE_EXT, SCHEDULER_INTERVAL, diff --git a/server/tests/api/fixtures/video_60fps_short.mp4 b/server/tests/api/fixtures/video_60fps_short.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..ff0593cf357e5b356125f0f96334096caeb1fd8e GIT binary patch literal 33968 zcmX_mWk4Ol&NlAu(BkgyPH}g)103AlU5mSWai>_JxVsgKySux5r}y6X`*HTn&Lp!l zd6G#sSuij#5_4BCM=KWxJ1{T^uz&xc9~;2kl-16WgB1)649?sc2m}N3g0wROxPoYE zVW2-hE7v5BI}etnThkbpNS4VjEtOdXtoB&?ilB+hI+oE#)TUH~UA3p;2) znh8X}te`9*#mGS-s`(8>X$CX}4SaKO^s)t-yOOZ6u&^_-v9NN3IxSsY9r>7V$CI@E==6_FNvUIhx1<^P-x>`BdyYP{i0!#p=f~+LYKyyKM5;LHQt%IqJ zAS)jW9}5Y<9$@R`0u*HNWane?WMyS1u>%TP0zFAw+)O|%ZW2cqFAyu}YUFGt$jZb5 zVgg-A?5sS2W=8*JWCd{;IRor1fP$=CB&L?m4t4+|5GyN*t25Bn*2)Fc;_>7$Gj#Eb1nM`j zb#n%I8JRlRIRack_og5bxjF-^>_Jn22%Q1{Xw01fc0d=9uuO~`y+Ca%GeI^`8(;=- z{I?4eBNHot%fE+MIRpQ*FAt!Vg{7+rXw1P8Xm4cU;0PN1Z>J-O)duJVnp=>KljVO+ zBRea5&@G9JDbOBh>gFoQ!SZjJ&VYX+bq2avg4mr+jsE|3|BXAF3Yt2blh~Pnh zLlZ0z3=B;f7#RxrsX$0>@br7J-{p8hP}krq^hOmrAZwjwL`MiyiHgU4gWqt^Gj>BI3Bd^gX+j|YF+$LP#s+BY%2E|Gcg zod}gV+Iu{6I^fOVxaNvAYBhAn^viNvFM4?C*Fb)y)A+m8q@A?5xQ-Ff;EJapKOD z1d+K=PAnP)&r}?3J?sTD>4gaGJ-K4o(<>Lh1Uq|BvesDEz!N-=KYvk~h7%?*t5xVk z2JM=|3kYAf!w{Ms{?(7*yZD=pAhLU*fb<^3rCIiz>phLbgLx7w+U||#+IGw$K^_j+ zWl_>(6Wz?>n!6RU29p1+P2^&gy%L172Pc$k8J4c@#mjAq7&*0lyVBJxN_}$El2g4$ zZ7?nEGqK4SXA1!FOy!ZKYvER711?x?F0u#B@e7RNByiyWGYqDUo8={wu}Zg(xzHsCQXzd2va<$c19Aw+$x zG;r4KL^sz2X%fn24-*u!Y}80xO7zK0=aAEEeuzlI(}nR|BU$kMRL&wy84M;cO(H@^ z>#F;%yyQ@IjP;ao3FkyL_9HYPf=5JytzMpvx8Iw_-q+G43o`bo!A`U+)}GZ!V9Q}S zy^XlMWQX7ju6vJ>MN|>lO>}%%0b&Qd*0#UHIN@Y0Z-y0!kpI+zErRE_ZMMxQmmz_@n4Y(w5CCpQGD|q|{B_hm#SU99P%=g}86*Op2Mb=;hIhD1~?>17D z$!rgaH`-ULu8AQ4oRRPq`C1nwLVU(f;n^Eth@%P8j~!!LA$x3O2r(V6f?3+-5MX zmo6ha(q8)t5WD<&Cyrb{@1wHEWTAakNt!vFx3og4Phi$$&t_VLd(fg>Ij*-mG}p~K zkLOgxphGaUkV>LuH(V2pD(07IRT*TcF_?{;>x^*jE-9LN1rkX+<1gW2H{^n9jK|C> zh5pL(wE(y_Cse1^)@Wf_Q?(I3gA;nu@wM>Vn4&IWF%qdY3`TvI+Mv1@2`+mCE$8oA ziJq1M)2fUDPG30J#Q`f2a9OfIiaFSCazsfC1_l-#CGZ2sWV>NHoSx4OxiLiC0dhaM z;n>A(6JzohJusi+8jX?f-u!u26u8KU(~*Mn>5eA+(&ohrJ!Pu*fsQYP^#k)Uuh0%W z<$i4%T+6y)Ep_K9$RvT{*tnglnf}fNYKu=<{t|QPXaWIYa`Yi`;%fz!GFpoG>l6a| zxtkMzNjM$MYO>V!a@OWnrJ^5mHQA&(ApRsVc+^7fLijgBd{V8?-QnhxbaB9+=Gqg3Gxqw07?1?^FggkKO4AR5D_`*X9GNT#7dZ(f z`K(D4m5YX0T@jpWd>i$p+j+lr)f)W+2mkir%Tx3=2swtvLC#p8Ks2d*iv7#G_l_69 z4&c|PTk&LS0vm_Ol=^A58duY=G>RF(r7i!(VaI&)yC=G_!GzrrJx>&*vGOIk#%GL} z4fopv!)y^6UjKi3Y1a>n0>VzkF`=azhHX*j&FL_bYkNsbvrNB*n(*}idq zWyFTSKMYI!799R|l$!j=h^c%v?dh2PCmZwhhghpnoh-sc_M^rI+{WkXH zty2faJEclCO(i;43t(sc6W6yZBa&i5 zJtR=+H<3lf3n7{Tk|9-Otv;n&$;$RfS^H>)h>YHc-F6;+S{?y2o7|r7CJ_&I!g2@a zRzZ%t0Qy;L*3OqqJjBzzcLZ}=a2I&_slGc~M-$eqrAKZUq!+qx_~!y2?Q47sM8`CB zv@2m+H<3_L<~+;o?vU0Ot{UsJ@YsVmqy>5^6OI*{PA2?eEuV`qGof#5N$;!p?h~w) zuV6OGvn{2LwB2p+{Us*LW0eB*3mu_?2tPT{h2mh$eX{J|VNi8}c|=r&{#QCcFr02b zOS_bUW5$BKoOxXLBY(P8JQXutR1B9PBP_xl7!9D#UN7MjL(S|8H}MtzOT2pgrTFsZ zG!oK7UchIW=rD0xPk=t8w|MmU5%2CDb$jih{;r|N136~!VM_(zmZ&>x$_%?`dGF(K zgk*e9r)JN^>TCIOB;{G8V(V-Ab#+t9Nf_3xtERK8jWavGxJe2yjxl$6Ce|Aq zml)uU9pr_O&NwD6bn3Vtq?+=44Dwg)vP7Vm$sc*OWtv_o4yBP~nrFdueVs8Ijd?rR zh~f=0Tz}k()aBi2lHNBg((t&X0`z(u(njYET$&@pw~5h(VvrOd@+AB!3Jj1IbG_$xyR1i_S)9Ktt!AmLk=Puu{UZl`_R#020biWc@!KsM<}Lp_46xtvY!m$SxG^S&)?UHMH@WK5RFR`c9Qgy23Wa zn8kPEkjjIECFF`SgXWKj;jnaMcu?Z9z3uvPFkA|n6~6=im#}hS+Pz_M7%JlEm<)af z#%*<7p=%)m8Q3)*;U8{oqz!pRy0Ls7Z+~qtGe<<9X@JkNr7a0g)E7fmbImt^yP}~1@DtvK%u#^_oRr%JVC=5RFB+pxRfH$TtU?7G9 zHDP#f(Kv-pVv;tEz9}=MSheYksZJ}B`#K!eVQt-eXP`(>mTdrwHM@>C1#VpaclYQ8 zz|et!|GsH&bOM~kaO2I1Q!h@hD(fOOjIm9Sw8YucJ@p9kUU1SYHFH(=;v`uuDs8?) zN1vwg9^n|z5l#+IRKs^`-^>hq#%T~)&M$$N{~8fYd;EIqX*GWO+The~jkp?LgwKDT zX@#8!3q@S?_Y8)^uy%l57%FhM8sI-VFZBIwraa5%wz2+=M|3wvGWQ!Vn1ba#>A#9>1?hZ+s+N15~xM0 zAoM_o=#O-=3t27t5OQ6*c`RJ3IcrwfJW|n<>w5#_EO?bKc9$07A(zfLsN>?d86dW; z&cC2mHEZUEFzGjt^Y~jL{0)MJKK-?d=Qf9o*ze;CSHporE!g8`gYS4DBWM#}sK3x& z)$276G1u~z`upGd_u9^~@AN|-MnP(hK6U~=t(FyPk})Z%h-2c{mU>>sw2i1euW0!b zy1)^5TS$-EP;QLtRCF}IKX%I>By`uzJ|_Fb;onx7gK-+?hY5KZWB(Ky#0$mz(ujHg znn$yjBf2EZcS%X_zJu;jEn%Vk+yCnX6N5pq_=qn=pDdL8k2b1_(%Fxn?2C}pg$H8r zTTtFk0zv~``+hN4dW%|rM<+3GAT%vsYFRjV8HDjv7-LfaO6&JOFII9C3AHw&D$AdO zV8%A{AIjlb!`%z?Uq84(k71E`(n~4{w&Ry*&tmL4$G=8bAVeUSMGi#bhJGlykuAvpZXcjWyEV< zQK)a&`jf+GyoDvJ0->0dTc3`EgJL%0uG1ILx*#dUJ+JSRj>L7Y z1G6g^MFi_ije+aAUos1`jw+aLlX;C*vIXG^aCO(2(bJI>0B3|6SvtY4nV#%)Jeob; zyxh*nV($Y80Xdzn+hFBIzj|PMZV_2T_+5Oh2mD`b65$7bmx-I(I_Pq!3G;ojQ)n&m zmkSOCZ}S9|DpUpr%rkJGMTF#rU$cg$H{DeyHpqf0lr({>hv*#HF(xWPfcFvJ(`TPqh2GBl!joi|J@*L? zKN|m;4&*n!cHvxP#B=9+;$ zv&Llg*`R)DaS*D%+@4w?0QcP%BtKAQ0d>S;tomR5*(1Kie2NF*44HmUa^?!+LQs2< zgvDws8bFi)K4v#3kzuoI6M2`=UDQDMSS=L6ZN>Nrfr}#ML$PGBPP)n^Q3Ygt@uZ#A zeORue6E_l7>)n0SJpYwDy`;<;i}5{*ASHJB?F3>Pu{sD03>-r=i_A8FcAgi2(>(vY z0UN1vav3}O$!*EJqieskXHIc=JC!WV8A#{4(pO82uslr-ZfDmIE;-IX?jZ}lr9u|O zmasiqP<3IgA4nc+Rg*`m?5TE+J$+PuJaNCwr4POl(2oM1EZ z?&G0t{n7c7WnN zxxWnh1+GwnUOrsp&EPe0b5L`p#Z3S%@$NT-ho1A9ODoj3mBUvk^<~NTx^}=M(?|V+5&xeY zy+yOc1xS-=BR;)q{O<2EZ+xBScWm`$7^_MU_oNA^*v`*`h<3;SJz`K3GxUS@;c=rJRIL7Lv}g8~8= zjiy&_qYh;e;l3JoU8&V4wX}>)^4R0`#dSg`mIF_!vAG(ZHxKtw{CPa(n_DNz+?_qh zE1^%7;P&>d@qu;rmBVL$V_FkCe$ls zi^&(hn6iCq*W_;yscy>O$NW|(r32~JwtlbsG77hp{@hQrlg_2uNnGr1dC3@#ej3DJ zY>X8EaX5)EfHfPRFBYuVy*2uCK0Q}N2ZQ^~L?wzYmivj_50pPm*5MnDx(kNyJrKdB= z;q;H4KY?cjr<*TZYn%8f3W(tEF;8mJy0nSkeZ2UBLdI?DoEu-zl#8#dd;A;`=^%&g zr*h1U)c>xG7;^axxq1BX4v}{!G>Ok%p+V|+GGhQN&*ipu))f;l}?lQF+>|mz;F^v(xL&WWrOvw+KcVFP1soQkaIRnHGcu9e7?C2nn+5 zjB4*mWGt!#!gvaYpz8Rv7Dh+TUcneQp+oewyTAA((4rs|N=36G{|^O zFMRJLXL>mUD{U(aazF*EUcO0AJaz52DludqL9zSp>cLC~A?URbjxLOK6T&x`Xunm! zVS=)uBy%IfK_3JMVv_*E1O!-3DD-KD2EPUD-dd zWB4)_sbEmUi4Y)wSk>M|4>pE^v*;v%j~ORT6fLJ9h~z z3&yhL7xp zkF~Iu@DIs~mwQ@t*xj>851pcpj4UPKMoyfqwCQ+3YK&+jn-Q(f4F*^xR&5U?U$?x! z)l3SPJE@w4_mb_;QP1+H-arylc35_6k-r7AWUQMSW1cr@0{>2~_9X2xmNEw+8X&b| zlUS1TfBiuw@&1+1N%OCXb~mgo9IV(XK?B~-mfMf?72+A~w3vJlu2kT8WguLuOH_@S z@&vy986RVaH+AdMG!%_ZKp3J;1Y<9QQ83y9VG0n%%klWW2@{Gj-)_TDEo?WD`QLt-mBb)Nr?is8EuwpnN}!V2T(io^8!p|qsM4?zMY zDLo9N^fuxspy>Gpn;L$ zojwY#>lroL5%S5$MvD$x-B)vImyoOQ#rn_6DKuu0_;TLe z?5f>I%Cm3qpceLn4BlFqhYa0(dqYvM0GGZ~esAa4+m7JezPR6ntnzM|=HSE z?3N#_KvD)C4S^UNLy~aATJ3`1dAF&$8{I>c#y4;cl5He?&j(LWu9$zn+?iAPv1ge~Zo3OV5eu zjBB7$4-kza`g5P_{KU%CC{(Q-H&z5YSZkj7Jwh!aDA=;tx#Y0>S4`Te8 zTh*adwq5aS9}$Ir!?#_}KX7bzBO&tQ8WG>MD^9;|K^43EMRdv5U4qb{2G2VMp+Q<8 zuG`gO0S0q~OAqCyU75;nS{h_G#ZWuBRPq=lCq)9PIx>l7UHu0O$j|Zm9*?#-f$N(C z5$vQd{0Jxs*>YKW43f%y87PWyBY7kEpE|Uj+;x;ySkXDo+X5uGC@|P=mCoq#$U1jn z2We@pn?Z=*o=6JwRlOAsU{EhG1RZrE3jZEpL(q?}CY85m!uol)Hf|72#c_%(GAAhx zHw82{9cES%<kmmSAtZ^R~qa$xu9BYDvrRLgyjt zg(sIN&|V(psT{h)%m_s*B%)abx5%kq${kXldHwrTCPLfkez-y`eLWuK6n08qS!s$&Q^x<=kv2msyNYkJ4@zC^47b@ zGBdpqWd!a|Xa#(QK$scV?>kpA*SKx`s5aBme^~YTX=|+d53Va!Y0mK~qS~oh(_Ry4 zGL_f%O8UzaRy03)Q4V)H8a$b{i(C~Oxp~mkr@yw8Lcs7I{IwozX-)AQXic@F0?RS^ z}A61NBW!|%~;l_hM`n`64D~f zv%0cEXtyz3fQ9L9-dsf(Nko|zvqyj@v5vlyx46B_HY|(w4JF&76up=2-@)o0lC!FM z*<*{J!1o?)iN&WzJes=qU>8CKDHp$JK8ggoz@~3cIt0&-CC@IOs_ZEKkuU96HIul8 za6hTz<}`Zu*N?woilz=jBSa@-H5`i5uIPBtcc&nB7VvyVS5Rn#L()Je8hqq{DeL#s zg0gb&74nO3t5;cpqIzU{xbH^WR{7E7lFb`{6vRUhp3e-@X98Ko%98%9+6U9#9NbCG zSxaH-z0;%28fvD^T>EX=r_6Vi+CY#_tBYoH$drp(X3C>}O72NUR1_7m>H&pDL`o9s z35b=R?nI>yaP!#i@kV90x5yM#Om^0BV2R)u+*c#jud7UG&E>yT{(WLmv#xED&hm1R zgyOUExFJXv2|DFI^mh${zC`_Tj?gHRzuEp9>I|<`{`7=HdM%yla;W3kZDbV{z)5%# znvu@W5grT@T|!5Zv|i@k1csHYNo*HZJV!h~8z)W{=>WXUr<1vwR@5zqe73*nh}y+0YxQ z7Xec}?`u>wgf+TeT=_KlKu8nn(Q5dj#IAZz1R*7rZ~K;Z9y#o0G~>iYwzaNAF?~=xZ|B$GEInMq#M(^O|Os`L`iVadHOT z%yTiKPuyyNd84KpO=jczHp&gSZBi15T)ErU^=Hj_mlpZO3{nz|J@ARG)j922g+l+bfgXWIlr5}SwRCWl>* z-|Y3lfVPk!QV!j5a4w17@=HG+5q*1~YShjniH0R63;zmahN~tQU814;1Go<&ZZ96l zoqW=2A?czASt|ZKe7SU#%q-;Yl17_@TQUwA+QDLNz9B+w(@pzBP3BTujm%aEsg7BNVBQa$H6mNntgXcSejDfU7UZ~5wlT95K>t{d> z9GnKGZ)^13mYxdIr?r=sUdZ5zh&G408>adiyDd zQna0CY;CC#x*ka9!;YVtWl?LT9f2TkYbKiQ4zdQ@OnEG&PY4xhbVgec+|ENrS| zW@6~x_WLIJ^Qyi`OKf7Sm7S7Ph{4@F^mXCK7CkNuZoeJtR-Iqy_rf-NeN@6lR25x@ zJd=x}NY~w5FvIWR27aQnwT5Y{E%j62!vtZNp~8Dusk5*-MjF8ObGwg*9^005QvYUG z{HSamvG*2+e9MoPor@m!w!gD)?^>a=3`J_=Ytiy$jdGi0?Hv0pr3t-uT2Q8Cq(ioq zE7|zuaLo1kI$u|z&G41lc3Qu6Aa4#$^x7c9o}UonZYmIO2XRUwaAB*8^fP-0sW#r< z=)tEJy$2Eh*MP^*KMr8_qI5jS<;lPAh!Bp0c^i+2<9&LzDU2xK62nuTL#{#=MazDsM? z9Hwq0ZRN0O-=I(Avt4IJ!D4+gHY||@+_^(x>NW62*aBzoUOi)1QrP7EBa2v$WeMcg z6(#4S0)ugTZ7YuKF#?d)e=JOR@VZz2q)jd_ZYC~q^bd8RPa)YoB^=i!5aZxv(4AIP zbc-s}9Y{n>6eabF55JKT#L}s-@lov#uZ{KNRuufp!*D``v-Fx^G=&9@Xp#7K;W;VU zwAe8OL6;s6rRiW?I}*41{@2Pn&*)ivDo9X{O>{ScdE+R~)9r|iHhAhy(YvSXm>^@T zV6(?<&Q#xR8neiez`JzN@dUk7GWs3d0s(LJbBy1cvIpqZih<)~#G)qEHW!vj4X!_m+}f`WS7n7?Vb9Orktbr9W2REUHf4BH%6M3@wjhR_cR z~h!F!z?5u@px!fu?X z%Fs#oKgs?Mj#r6mg^^{IM-6$^cM3#2J|q!Y2fGN+{NiDXDvxAYeH7RBTPB;pyXEK@ z9C{m>HoQ0mO9f9*t5ve9qbY0O#8#rIj7le7#E#ci=6kV>I|O=H&7NR;4%viV+Qa|@ z2JaU`^cG%6i9v=v-kpV0dT6Cd@=~3|$G?8{CH|X{(N?pSUvVB=FUH}N|48Hd&A1|! z!oCf#$=IfkfAQE?^goS6ll%=amr?U|c~6iSoAOy_CK;M){f+*BBrF*(%D2jCgr8sG z;x;iAOj?YRTRcnw(LDX83+tE-&Qh;|vsgagL%*?1IN@Q6doqa}H{tn~x(e%cuTs#~ z=7@@qJ1;5*mxsW%8MHsD3W+e%w_geuL)v((`*YgZ8LdY+8V zB4{Vu_Zuww5LL;T1^XXFXUScV$jDP;T`81=Q-Ri?;~UICG=D_``(L<~w%?hMqT-ts zkz`4p-_deGvzGma*m@b^tz!idi7^YM%Fz8_Jc!c8KaFWyteED*cX^cty8!vS(Fkl= z_~>O1^K6+QhG9dXo%QmkxmCr8I#l@o?BN;YgTfZHbz%@oJ9&YUS;xEP4eOS(WRiRW z99GV`?Lm&>cg*gf#ef2_SCH-Cl##0_1p%SvD9dqWn_4QLhSiMbBEQJcW^s`hkD56c z3+(J@fsVNp(d>`^24YyBbQ+@1o_X$Nz8R9gY;Ih-Ym}kT-FjA1eO)AZep$8s{6zxu zuo5me^%8I&9ti`}S{IL#gCikav-B?hLOxYFpF8yD4lNUFkVs0gP$~*D(V>L&L=iUF zf<4YVkl99_S$8A|9Stf$dh$N8v|<7xT|%`kr4kXgho90AjhV;l!x$>^I#&S_|MOsX zEWR-8Lbrl6Pr{3H>v^nxH=`b=5V1+(2+YQy1^4Ite52QjYw|)w$R!wXiS+J=_{95t zuL_*A>$+eZ^vNT z=}Z&bjZ}8OTYs%>N=35xyx+RCX??}~X4KDG!*Tq^A&fm_g*1?j2FW}1eB{!I8KG%> zy6Y!4?~4M-hJH zLMgYf&s(+4q8Y1Wy}n%{Z#O*#LJWBrO*5jM92jS#$COIY|HSCvNy zf{~N(9NSa#>^pW`IK*he!^kru6BqwB_JSz!NjRhu(>f`g3X zPs}3YC6jGe$W?6GQ%Ut@M5RJVVJS`(*ucF8K3gLE72qYZN6$)@<=B-z<;FuCdRu7T zz-^m&$AOLz(aPb(A|BRTqrDjhMLw* zvlZPUFMiwQFl~HEnh#ABC+YtXKu*-(*5WmFS

rbk0l@l7!LaZ*%+Z>744{h|U8#t9EV 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 literal 0 HcmV?d00001 diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts index c494e7f67..ef929960d 100644 --- a/server/tests/api/videos/video-transcoder.ts +++ b/server/tests/api/videos/video-transcoder.ts @@ -2,10 +2,13 @@ import * as chai from 'chai' import 'mocha' +import { VideoDetails } from '../../../../shared/models/videos' +import { getVideoFileFPS } from '../../../helpers/ffmpeg-utils' import { - flushAndRunMultipleServers, flushTests, getVideo, getVideosList, killallServers, ServerInfo, setAccessTokensToServers, uploadVideo, + flushAndRunMultipleServers, flushTests, getVideo, getVideosList, killallServers, root, ServerInfo, setAccessTokensToServers, uploadVideo, wait, webtorrentAdd } from '../../utils' +import { join } from 'path' const expect = chai.expect @@ -78,6 +81,34 @@ describe('Test video transcoding', function () { expect(torrent.files[0].path).match(/\.mp4$/) }) + it('Should transcode to 30 FPS', async function () { + this.timeout(60000) + + const videoAttributes = { + name: 'my super 30fps name for server 2', + description: 'my super 30fps description for server 2', + fixture: 'video_60fps_short.mp4' + } + await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) + + await wait(20000) + + const res = await getVideosList(servers[1].url) + + const video = res.body.data[0] + const res2 = await getVideo(servers[1].url, video.id) + const videoDetails: VideoDetails = res2.body + + expect(videoDetails.files).to.have.lengthOf(1) + + for (const resolution of [ '240' ]) { + const path = join(root(), 'test2', 'videos', video.uuid + '-' + resolution + '.mp4') + const fps = await getVideoFileFPS(path) + + expect(fps).to.be.below(31) + } + }) + after(async function () { killallServers(servers) -- 2.41.0