From a9d586e93a8544fe952fd271038e584cfdcc9787 Mon Sep 17 00:00:00 2001 From: evazion Date: Thu, 27 Oct 2022 00:44:36 -0500 Subject: [PATCH] Fix #3615: Unsupported video codecs. Don't allow uploading videos with unsupported video codecs. The only video codecs we allow for MP4 files are H.264 and VP9. Other codecs, including H.265 (aka HEVC), MPEG-4 part 2, and AV1, are disallowed because they're not universally supported by browsers. Firefox doesn't support H.265 or MPEG-4 part 2, and Safari doesn't support AV1. Additionally, don't allow videos with multiple video tracks, multiple audio tracks, or no video tracks. Multiple video and audio tracks are disallowed because they're rare and for moderation purposes, we don't want people hiding content in extra tracks. These restrictions really only apply to MP4 videos, since WebM files don't support multiple video or audio tracks and only support a limited number of codecs (VP8 and VP9 for videos, Vorbis and Opus for audio). There are currently 22 posts with unsupported video codecs: * https://danbooru.donmai.us/posts?tags=video+is:mp4+-exif:Track1:CompressorID=avc1+-exif:Track2:CompressorID=avc1+-exif:Track1:CompressorID=vp09+-exif:Track2:CompressorID=vp09 # AVC1 is H.264 There is one post that has multiple audio tracks: * https://danbooru.donmai.us/posts/2382057 --- app/logical/ffmpeg.rb | 16 +++++++--- app/logical/media_file.rb | 10 +++++++ app/logical/media_file/video.rb | 16 +++++----- .../components/post_preview_component_test.rb | 2 +- test/files/mp4/test-300x300.h265.mp4 | Bin 0 -> 22148 bytes test/files/{ => mp4}/test-300x300.mp4 | Bin test/files/{ => mp4}/test-audio.m4v | Bin test/files/{ => mp4}/test-audio.mp4 | Bin test/files/{ => mp4}/test-iso5.mp4 | Bin test/functional/uploads_controller_test.rb | 17 +++++++++-- test/unit/media_file_test.rb | 28 +++++++++++------- 11 files changed, 61 insertions(+), 28 deletions(-) create mode 100644 test/files/mp4/test-300x300.h265.mp4 rename test/files/{ => mp4}/test-300x300.mp4 (100%) rename test/files/{ => mp4}/test-audio.m4v (100%) rename test/files/{ => mp4}/test-audio.mp4 (100%) rename test/files/{ => mp4}/test-iso5.mp4 (100%) diff --git a/app/logical/ffmpeg.rb b/app/logical/ffmpeg.rb index a89984bd8..35d0b62f0 100644 --- a/app/logical/ffmpeg.rb +++ b/app/logical/ffmpeg.rb @@ -46,11 +46,11 @@ class FFmpeg end def width - video_streams.first[:width] + video_stream[:width] end def height - video_streams.first[:height] + video_stream[:height] end # @see https://trac.ffmpeg.org/wiki/FFprobeTips#Duration @@ -68,8 +68,8 @@ class FFmpeg # @return [Integer, nil] The number of frames in the video or animation, or nil if unknown. def frame_count - if video_streams.first&.has_key?(:nb_frames) - video_streams.first[:nb_frames].to_i + if video_stream.has_key?(:nb_frames) + video_stream[:nb_frames].to_i elsif playback_info.has_key?(:frame) playback_info[:frame].to_i else @@ -83,6 +83,14 @@ class FFmpeg frame_count / duration end + def video_codec + video_stream[:codec_name] + end + + def video_stream + video_streams.first || {} + end + def video_streams metadata[:streams].to_a.select { |stream| stream[:codec_type] == "video" } end diff --git a/app/logical/media_file.rb b/app/logical/media_file.rb index ebb145378..9b74ef336 100644 --- a/app/logical/media_file.rb +++ b/app/logical/media_file.rb @@ -156,6 +156,16 @@ class MediaFile file_ext.in?([:webm, :mp4]) end + # @return [Boolean] True if the file is a MP4. + def is_mp4? + file_ext == :mp4 + end + + # @return [Boolean] True if the file is a WebM. + def is_webm? + file_ext == :webm + end + # @return [Boolean] true if the file is a Pixiv ugoira def is_ugoira? file_ext == :zip diff --git a/app/logical/media_file/video.rb b/app/logical/media_file/video.rb index badab7be6..ac371ced5 100644 --- a/app/logical/media_file/video.rb +++ b/app/logical/media_file/video.rb @@ -5,7 +5,7 @@ # # @see https://github.com/streamio/streamio-ffmpeg class MediaFile::Video < MediaFile - delegate :duration, :frame_count, :frame_rate, :has_audio?, to: :video + delegate :duration, :frame_count, :frame_rate, :has_audio?, :video_codec, :video_stream, :video_streams, :audio_streams, to: :video def dimensions [video.width, video.height] @@ -16,14 +16,12 @@ class MediaFile::Video < MediaFile end def is_supported? - case file_ext - when :webm - metadata["Matroska:DocType"] == "webm" - when :mp4 - true - else - false - end + return false if video_streams.size != 1 + return false if audio_streams.size > 1 + return false if is_webm? && metadata["Matroska:DocType"] != "webm" + return false if is_mp4? && !video_codec.in?(["h264", "vp9"]) + + true end # True if decoding the video fails. diff --git a/test/components/post_preview_component_test.rb b/test/components/post_preview_component_test.rb index 06003769a..e80b2dfbd 100644 --- a/test/components/post_preview_component_test.rb +++ b/test/components/post_preview_component_test.rb @@ -31,7 +31,7 @@ class PostPreviewComponentTest < ViewComponent::TestCase context "for a video post with sound" do should "render" do - @post = create(:post_with_file, tag_string: "sound", filename: "test-audio.mp4").reload + @post = create(:post_with_file, tag_string: "sound", filename: "mp4/test-audio.mp4").reload node = render_preview(@post, current_user: User.anonymous) assert_equal(post_path(@post), node.css("article a").attr("href").value) diff --git a/test/files/mp4/test-300x300.h265.mp4 b/test/files/mp4/test-300x300.h265.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..c8df94aa8a7e83ebd68326de6aa33922cf0cd228 GIT binary patch literal 22148 zcmZQzV30{GsVvAW&d+6FU}6B#nZ@}=xdkSM3=9k$X+^223=HmExhaVy3=C@jH5k_m zYMeCOeoCd`$DGfO#m+l`=h;MeD$L-%LI!R5a(c0lQNq^h_>}lbT6UB4+=g#yNZrmBT zWZAn32@|;Nu15Z~5^Vd^diK~FL(jO9n=G-K&Aj4MlY8RRPA#x6zc+8*qi+od&h%`3 z`QxuZ#TLJF+ZU*7@=o}*Ci++H-ZfocM7b4aCEV!q{oBJcLF`AGl*b|erauo7i=TeD zk{OyT<0|pZr6>1D={}zxp)=FV?H_80+09*Gd%(%)s^FQ;hHsB75IPzxF8J>F(*tjl z7Efa1Qogr#bPTSdk``zUPfj zgwbiaOd+!Z73S|zc^q>dE|pk&|IzUyjjHvnCUch>#9neqvII?$5sN^0HO;p8apze$LNe;z{8QhHai_uC?rlxz%NT zk6}Mc%h9lZ6SUke>FQlQDmAfIAar5MAG6ty7M-3V!r{E+$osVAll2T9tema9{nP60 zs~7LDeez#BQ8&-Byz+epgVcQ0z1PhcY7Z;Fy|mYO>vk;>_lwsiib(C9=~Y;!Qa8AXxOHmCXfKe@b_`8k zsD10=uNTI~I->hU_j{xzZLT%%S5Zm$u6!ia?Bc|Q%2Cgfd|x&miN3`xTN`iUs+@HD z^E(amwdymu+&I?qIu@31I~{gqp{IfH+|vn*9vxosHN~GY|uGnYpm5oOc- zw6}0Ug~l{P?$6h(^a8F(y|`4)K3T?P!Cx(97hb;udaJ*!d9qaGRFl^v!9Lsay;*D4 zq<%QQyK%qK?zSvWX7h%#TGNuAG20u}f3j4Nd-37Dh|8~u7cvsX>rYgkoVRaVEpy$B zkT-eZukLYv{J?!EReJi5626Vk)-m6n9h&9E*Y0Q3#M^0o{<}9%;-+2S!+$Ty=3!Le zQqRpQO}zg~r|uND+w%a!>~~+wU9D#=d1X>pJApqt>-cAW+j@@nHOzi-|Ch(IZ~XmD z`@X}erN<_}?>}={{Lx-+vEN0d`fW_|E7s3D9lQDR>_)A;y232|)v=Zni#T_F5DnP7 zj5lm$UzEk|murI3_1|uLl~O6xc7DUb)7|?Qo||F4I(@Cm;VZspXK!>DU~&)s@IBjOQ8f<uatf}Gxf{9{6D?g8_qoo6PfU;EXDO$PjA|- z(jdvWW6dQ~*FLfNR`=zy3~S%q9ck_s0-sf+o_ae7-|kmkI8WYxU)qU=i6<{!4b?xp zj6?OI>B5B@Jhijzo6R0CTzf7($(iYaUw@a|x_{ELZ=jA*g@Ca{V`wA$u1Z@%^rK)Q&y6b6Z;JotFt_Pj(Aj+{Cmw69@&5ex=xW)ILAILSasJGk887tmR^}=@Ywf=A#I*7)e`2fQ z^o`Zs9l{}Rmp}iheq58aDQV*SLl+JTF*2B>O>*C*vG3;3B`4>7wLAXVTyj^jsof49 zoqt`rT*{ZNP1d;UOj7ZB*DU%%@wfB2YgdIDBX)&_^%VMvzMVENn4w5*YmI+YzQg*3 z^Dc1o$aw@US@JFR$IR17yXje7W z>X@A}Ip z#M776(nl|BYG9KP585Di>)+p{&+_xMv=08Lmiy3i<)mKeiD`DpWs@8PBO~maT~h+M z1DAhsjQxA0BW6`-?eBZRXV=YPVcXz6#X3pkNS^w;q~i34H`T8Qot{rsxi!Dks^*?j+9R`7%++Z7J&_mn4wI+W{}m{H`;g(*!*}V@cMF0vG93;# zU64$k{BZts4vmLO3D3Cq#*_e{$a;!7q>}SY zg-0AEzB5fpvkrQZ%l3DFk5WecQLgCh9WxzdF~L&WFG zz51nhxKu%4`{P@m?kuX<)pjS+b7s-P7jLs&#o3>4+45pZuj$Yp~s(xxh5`>$N|+#fcFW zdt_!CF^UOu%$zm(+f<*nZ98qRv%Z`9&3ILjXySj_-~;kM-b-lw4+~g0OH^Qo^ul{e ztE*PBKgyHaT%=Xwv$LJqzW!&~vH#{dYjj{-h2tA5_8KRuc2 z`@yDv4HH((on87l@S6EV>r(0UQ@vj0l(0YiWV_7utFQ0cKefKHCj$2A`~Bm||72TW zu;P~6dGYo0*XKl)+1)$+s`~jA?tk}Q-fZ=H_V|Aw$2zVUnTc)xjIz5H_s((G`MK_e z$KEE9Ki4f z?2j(!OcC3ew`bc1UB6dTIrn?*@su){U~j9waQ2dE9R-!eiO$CVW2ysO8FmmjJYrCaIBKZ0*-D>0ud6l`If8%darS>_>M^5@3@l8=FirC*Y zWrqE-Cx<3&Vl33N693pCZR@{ZI9Twv}i*R)n0 z7U9Czb;4ZTXQJOEzn`#vj_3S}kNzK8E!VgAJ@-h>`7P2peb*GL;`1N>M)DSE)^z%Q zzWXyk?2zob$(c#9_b*Pn;I(7Rzuvfn{TVj)d;U&S^KE~8drHx2_PxSe*PTsTdrSPz z`$k2pEUtCGFWP;z{`+}m=4U;6*czcctw+7P zkbj3*acR_3eUqp=PS*o`B)1F39S(T1OIyxjhT|)TWsjyczI|3arGL#W(<#{^Uuza! z&cFHmMPAz?gNrkTuLaDSqPyeRU8~Pc-E#WTM{T!q-Tvlt;wuCH`CF@gSQjvCX{^7) zdi!}(cG#cB3w>OskuyTRn&tJe9X-5SQbXm|WAo!rwr{(4BhzEf#;VnSDEsPQ$%>ze|O zN_D!NHa&de%@H(7tOD!i-;LE} z{gwN`^x(xCLJJ)%f;w13l2UBDRvZfnTiaUnH^ImEBTupa#2pjG-agDd({CPL74trb zZBqEol@s;WUrxC@+dHMFW;WN^|GqAVJ+G^Hgq9U(C+^7T<1UvvpL~`zjV-C-i z<~ZN3w4~6>DE*CXWjytE?;&XJIYz~=7RRMv|D%AHz&OFezj2S)INs4 zd@t+zau_r%wuF3Le`D|V+gYzRJnN|Zdu__$tIITMq~$u3x#wpd4c+l1=D@TCol=K* z!^_&&nlDSb>FT=r_jZG2jf|(HHEt~8<9?F0esaXTCEPie_8dRICRtEd#s2^ET|zyQ zaT*fS*Jk~%TDs=cl;FOHeOBxJ9^E)&_2|^ftxrXcb+Y$9*z{eW|8wEmaPLP~xn~|; zwmay=!8h5*YdHNS?Dkpny-w0$_!och{JA^ocdKF+71nVo_dHFnsgONvxns)}!3#&W zrR`zg!LoDBsuZV}PxRAH96a*b*COgq*oS*o&-v%Nt4`@xKKlQl<(Z7$!iKXg@7lK; zxv!ZMQ}u2gC0~f!{vXJhihf;y!i5=Dskh&pFLdE#+q0uv z>Q_xqFRBWDwWgILxya2YF>g_4omSt8l`LD9u5q$stU3RG z<@c+u>YSJPvy@egyC7TGYsTF7N|}#N%+r>z-t;x3!1r$YX9KEFk${jwk*-BQm-^O zZ+uysEcb7I@c+k?6CSNvz1x5KiFy~I?`oxn_a8Z#hpOE4<7{ew_Rwwv=O>@uO|OgR zeskeGG(}?`r^uRJXH8Wl6Moh3%Gw!obxh9Lm@K%fL^J(|xX7J5d~4V$W16$oWrB;> zHtTQub9+bdJZt|gay#yq%UPKAcfh#M(o20tX#_KNkWUdLWJAVCr zd%V0vu|=oe@kl>8iC~s~Uct0`p-S<=9THWGBe)XYO}hH}kORNRzuV#q-Q?eGDNIPY z;>f;PRpMFyF~c-l@2dCj3yk^pFod&{b-zJnMM7|MLgi`>t18yTHNBM#ZA-c* zz5XaIbU5hb9>!ZoeYZ;}ow*~;>2>Gm8qtZ?DP2ar9BOBcJ z=Tz8Pp7A;JZX%m8e{*K!9|Mu(%2R5Cx%QC72PcJTrdpAYB z%v1Tj|KyeVcNM#y7)l#BF1Y{Y(w;BL^9m=;oMm=<-E~Fnyq3)R#Zy1uTq~*d^>**S z5{YX0?+1fb!Z)n$?5}?kc%@u-Rh^jOuFy-@WN-R*Zar}|dHdVxZG021i^qk${b;r~ zZO@v%G7U!EYNPu%I6W5WrdkK5h~zh0RC!+99`|#pqpiRurpI3mk8jF5^?LJElZ5q} zQ`Us3Iz%l?P#o8~V2Q26@65Si?nnaQ~!(X*(Bhd9ik;(Ze?jzW;x=pQm45mDi>2#bO8*OpDF!zx5A3^kmX&{lS?y_t?5fQ^>2(eL8IgC*l-@iz9cxur zAnOs=waw?!qN=(TPgmYg_Whf3F3@T<*F!rIwW|+1EBu4QPb!5gY*e^sZG6B#{5;oz zhL`{5{D1qe#Ntw8&gID=nWx-te7V$JcD(AvEq1+)tEBw`*_J=u?ro=#I7>Km+ms64 zE74Pn*rOaXZ@+C4H~SZz|9`jcf-OQHcZ!!4L@3<}nDqW+O#Z{e4lU2uNjv`$f6TV) zQH77;^Pf z<%Sx{rD^X$e(lcOsB7^DEc{HjZ^yi>y*zY zH3C!D8!xd>K6N4Nb%pNUvY1aDOE2x%{A1P2&o5>+hrCVQa?gGG@9nP_Ga60cFEqSu zR_wj6Ze!dz=Chyw?riekZ^D^Ur*rDAwEd~zs@4Ps#$v%uvs|-QsOLQqYqi?u{A&Tv zzH9SV#=gCAINi_o()7^4x??T7nP#1SB_y$0s&bMN^n z1TX4La=xB_{reHC8{ZaxpX6$C&-4A68SY^!jo)*F!Y9p@u~iiqBSQ!S-+Sf z`zCGG_q4wUu3h@}^wf{NebIiE2U2!TJ1*aR++6H&qNCO!gT~9^k7O4vIr#r|beQfm ze^ECDaZA?|N7o3oFLycpZbtFvi@#UNb8UFtkXm!{<@QxcyH@a@dStbB?`@r(>5Jzj z7@U<(U3sj$;egKNFW>9FTW!#9-+XAkoluds`@F0l9um(Rx98|4)a)v1dGtWvW&7NO z?ltAizo#_)(X;I?wmn5HY9GSj1 z`&pE}&)U1Sa}RZISroa#KwVUGFV`&LtNSlme(PM`b@Seim3i47Asza>RT`I$%;IMf z_q6g_r17d??W~hhOQo}8yA}Db$2r^yH9qX}Y0tKnHx;RzCnsE-ci#D9+VZvgj-^>j zE|w}#-t_$H{jx8I&x-0T{<7q!`%V8u$Aufurp(=#q`KsTe&c!JvlA!$YT0e}_nh>v zpEp-)rF54sHud{woA~$y;~w=RkAE9p<+(h!=X~Og_Jg~=`Rx$guIeSorX8;#BE_KQ zG-ds|FKf;F4*ZynXnhfuC*4|BW}< zU-g7c4RiRz8ZP>eo5lA=dZ7Y@Lr=GjBY*ZuQIFg|&CvH?EjjFM}j!yto{nlxwe4 z6&4?qxAVfjirr=I(%Sm`cRcqOy!yJde8umw8UMFguGS0RYZP#E@jf|L?VacU3C!GW z!LPh?{+X+FJFNw8elRlKto63r`J9@P_F_bz?*($$5@k=&mv z&-uHWE51Crdy&qhWA0z({bl9ME7aGR&+y@~xtNa=L*hZk!>i`}Jh$*-;+jgO1!dLW zM2;SQvfcRg57W7_DNY;v@AZ`jpE~k7v20rHq3K$ZiqR^`F+>>*jEC# zWakM}_p9&Eu^%6txs051xLT5B+dto3 z^J0%Mmu}(ljP8zi-1m$ayUrb7wfVBX)RtAcr3XH*T=Q+)wpNqpm*4Woa&+7gd&|fk zy|hmEjPk?trth8=eBX0lJlg(8QB}5YQ|r}=ul_97M+2*0Ca|#HnlHSylDDXL$BDG) z-2n%lAH03&zRb*|wSw%|kG3W!DXP4buiC2JX_w9Mqo`)XjGJG!d~*8z^T7MFFLpRL zRcUrk>fL5FnYDYGEQ+8_O=%)nO42f9)u_DH(9R&9Ua;WL25j zu?#25Z%ur>2T)g?MbZi_g{wH>^-OrH`_w{4{oOce*8yyziXOr$!H!WDS+A=j>I=JeWp9{;8D6^CM;|%>PCkMYu z_MY?O`Re<|w{Lxz(|JK4(DHxhnhzoiF6}=lmiV;8#&j$9>9>9yo0JS1Htcb@d98+L z(j}ftpIlf24Of|@C$zWf#_ri}an^i(@p_Sj6W>i&Jh^4ZF2{B2p|H@Tz#2n` z%*k;|`_?RzUZGLPd$Q3b^=!r(sof2|)b`Ri(RJJ)OJ?ALv-g_9$MKRN4$zmBN<(DTp2 zw4*VOQ9XCuTNK8VBoHLvZt5jQCh;jjCyIyRm=97&Uig5d#){){`)F@ z4XyuyKjzoHi~F_uiC~_rn%0apYr@JI)-1kvFX?mSamg^=HOa!KcduywRApCC*}=jt zllJ~j@K2Aw9QL0?ixaJ5A3J`|Q+}hAk$kcHru*L4^S>vo?)u3-<5By&FA9MSp2AiS zj_SLHG#$}1m0|TWV0Yt6KeyUHYLOq)O5UT@JD76f*MD)c5BSDZ`FoP+_8B5ffx)s@OBU6AOG61s?f*;j)9^r#}WWIX=l2_ytAXi$2_Vby`8Ix8tPGk%f=i)Fy;aw|~eWwr=@{7DWX``J(ST z4t6j;-ukwB%Hu}|b6cH~=Q6DMzcjQ_O3b?Mdf@l&V{2#hT?=%+WO#o;6xa6(Tfrxj z<<~y}e`uCC((rGr^&iFjsyWE1cDqQ#fq_d4pn>}LBcl|KXhUL%-4K2#kvZhdJ0%-KkvoCP#jSF$ew&NDEws+s8hM!C?}o;`uBOKa z??1m^DpS1iVxMEh&EoYt_nc|jx%b1Kd3BPUy-S1~_ZqxtN_5R%*pkGf=Xz88?dDr; z;j&*v=Kl40@u6^zlTU~3>-pU-Ywo7L-pI0<<8H*~C7F00;qc+Up%|F*`_a0(M~_0( ze?E$K4qh<%Y0|#0{%b9Y1>Y$#Oy!=g_4bo#pTFh&DMb}BM+1!V4BWHoKD^n`zA$Q| z+d4k(CkcwLI3u64b;@r4l@U{yv?lX-a(01e#)Y5%t*6)~-0)(aZqB>$ta+r^(Pb{1 zG=9%~*gB3sOI9yl&z8 z#=B08Vgau@$Bn$Yv|-n5#pzf=#I2i#xGeLgLFj@bllCe__v6~g8E4vL&xx>x$a zs|9cBRWc?qB`VGCG^n(P&7C-S^zif@Wb;19y+bhm3-xdDjmv_@Q z*?ldOY?Y0d={9}V^X^vM7A>#5G9j6si9mOb2L;*h4wAJ=AEIDh@e_e&Ore&5Qz?Bf}xJt1tXS<5F(?akQoe!30& zE~olkz882dtv9*zyxc6JSNd(_5@G$D3?YqCO25_}OFH++O0nRk+J_&rg4Ue$*j{M< zgy;R@r04gZJI{RRad*OjCH%VIPaV{~Q~b{5y5)IBmleKFH`o0RPLMjgCZy}o#PXS? zF<&i`)`@TgElA^-s@vb{sIPN;+K-vnl`lTs&i3Qso@EhVLl1}^mG^#O6sY8ALZGE8cKV1>u`QhTN4bxUtH5GRYLHf>9kf36gEZF%@J?@M~LZElOVy2R=Wv+`LN&+%Px zZ%vlD0-N6K*WsHpYt*>z?<%`pxiq-ppM%s5X<1d{b@A`?8l5xszI!H1`gUy-XE}QD zMx!F*(ltr>*1eW5L`8kO51E_S>Sg|w-&cFf*-B(@%$NJZZ}`$8t*Zhbi6`2x-ovH4 zh;P=#sRq0m8Ja~NTaK4Ro<7-X>(8cin7gKM;|s6#4_t)iblyD^cjWB+%XUv*$$M_p zX=uLq$lQYU-go6$$vxS-MAiTA${LIQlN=(6hWD1YOm5WkbYH;0z#z@U_#kC&MN(^_ z`r|h;g$Du|xf!@`Fn{{$z?27)W?+1v@-=?ZF|S8nk8A5X8Uh(Z8Tc|XPaI(U43&1d z9>3@q7glK*2F3?%+yA;5gl1+28Nsi3=9m)OpFhi)WhQoyF^dR z%(QRdz0vpc&AYV?F&+9G2l6B4^)obs0z#I7@xigLpITn13nUXxJFT`XICJ-gadL z2Biehx<>}ahiPB0U(!igtF{2*evs7+YoQi{T(?L!{JX?K01sI9u2W=Ue59lv z6n-aZX^}ZNa9Q80lpbL0KJ`LEqv1aT7bwPL7#JTu0|l*ww&w~gPLp9^e8dI`S*-Gk z42+MQ2m~yf5EJ8t1Dmd1zvIHD6d3bo4hzGCzfqAh-M_E}%zbGPV*R+sx!|fmlWxW9 z7xTW?Myvk(##MApAThk)Zl0I0q5my4rmt~Db1S~IO*-u{=i1u;TgAK=UMhM%H*WSL zx!+REaUTyZdG)kW|44ZQuh67JeI4RL6PoodNd9;`Q!_f9wW55}(Wh^Gwy=MnG+p6_ zo`p{xi~qb8m8y14c{_5lZ#fDXJS>lY!IZ30@MKBLvjdxi@~+%m^DudB*FRa~D@#JVd5kw#f4ABjeL?Js zkKjjJn~yK=@pJ~T-{{!!LT0P1M4c<8)FMrpy*1s)?7rjLy6L+^IVI2EKO^dr zeg7KAx6^_OaqJyi`7_xU$gO_#?_hicQ*PXQU)jgWE%MxJ;sdH4oOxjPHT!|O)a*bAPgTzvqJ~(|InR)XfRAnkV7F zHbFXKJZKiGyBm`p3T&kPvCe8WO(XwW{P-_3GN*m8(AuW@REm^3!Y@c@Y3SZGp z6Q`V*QjvVs>XUhOd0McGBwa&&8%~%ZlG&ePqF>FMAeS zzF8V^tY2kb!W~hT&MFV<182SS!W<-*x2{+o^Y8Mv%n6(N?gg0UNnTCfcc-S?W9Er_ zUoy5|ndNnUuTE*?htRF*LCi1p*d+g|8Lr{kZJ)6&U!dA2DeCN=ynFM`xlNY46maJ# zx7?cAGr$0uurvGct} z%G)B}nRHjo+qqa;f6kwt7x%vuuKd0xRfkEVdfDCuWih$$CLM^o)F{+ZU~aIbX<0L$ zgof4H;uSlrCi9(GTXZ7AdDH(B1~wCNulhLeW_vc{tLC=@o$h8&yo=-4NPc+e#Mqjf z?O6Kr@SUjKiVB&g(@gBoS@$WPUg^Ut;WR@(ttIZ(1=rFLF7fL5Ey}j2gt++7B z>lKsok0Ty3Utc7ybPam;`@lQN&h-Z$%vfYJEmL9HOPCCdvXBL` zf0@#ryye=nTT4%UYU$#XMK75;H!~hu`5{E4N-3}N!1Y#>wOc;R7;q`r&-j(bteK*@ zvj1;l#K~P=Q{>iVR%YCv*_9VD|B>m^>0!6B{dUaW$*eouIFft!o`~dvC+RP?tUIKe znNc-+dXtZ8vu;($uNNKDnI}%$to$uYMdY&gy7?PVyjbm9a&(j2V!PPARY40Ig{~cI z?MT16v3&B;rYk>Bz0y0xb>Z;Q>o=Q>y%wAoPmJf78EV16z@Wy&_~?*2sPoQYgQ;Q7H-o%&a!i3w zJi}sltFLT7oVEGK^hw);t-P-Cbg&=Vz3hzNXElz)ijg_{+sa(;ZPJPhY~41k*TQeN znbz|SixR3;T8>TMW0A9gwJC`0z`QiSU7_B~o~=w367oFDc1U)%^KYT!a?;y(-%#tZSdfu+3`U?6#gjy&sp(pS`x~D9d+;t38R~Po=E5 zU#}BXvUq8C(L(lPdfVcnGgA5?JE{*$9o~PcFUqd9MzK5NSeBup*0gg^+oWZeNmnha z^?l#vt1|Udim2R;byXL3#8!(QE-|{QQ#Mz~YvjFX|CUgo@uT3+x-3i)7c3#Gi7Ja=J+Ki@qA+F!;DwY zk|cAP%tg-d{gXVKA*uJ|H@}6~Ijj67KNg?WOS{#m)OlFKRz^xh?=-v3z7@~>`jYHj zIiHz-igrEo$5Aec@8sSs`&*Oec7MC-WP5A6@(i8>53U*K+P=SVfZ^Q<&!xvKCW}qk z_TJHHlDYWVOE3K|B?nxUT(wy3@DpAA_~carUT4&p9~Zt}$h;#{BP0Lt!sEGLx71Vx zu29O-toOVl%JMbdk-_Fwk#6tbyld*ViK;!!x(n)!j4tS#Z4>-AFVgmf9V6#G-gfV| z&DML_y}BCWXGQJ$W$|KHlPjP&af~oCv(c9 zl6n4%4U`LxEJ^x2`>S#Bx{w54-vsfZV~^5+IP%qU=TH6 z0Jlq-4lyu3S_W?QXv`DtVPk4o^INoDM{@OzS1Uhne|nWC_4C$*mpA+(N_b9-_cwD( z{g-9FQz^^v=j_F#qy;sbQydmo29=(33n)~aoX%UkeBGhHWd@TJG-5iHa&8~9u*FSL;%ic%<=Dcx|ja99sOEUw}u96*~}4hxP9a6#{ZVaP1+h)VlM4s zUY~O|^Xjed?{jCq=gsEn-f92M&#W>|;9RWNi5jPg)V2nr1QbuDIFq_0?PdE$MTn{bn~%l(Bj}?}*?r&W#VY z7qe%u`BZ$`7%23KY0K7-m0L^KPnS3yu+^*Qtl6C)DeJloEZ1cF?5k#OFPeEg?Mt=y zT$KjZ38iOCH|1n4&pIqLDgA<2jlQS_fmcafeQ=_X`tQ}sBQiT)HWy7t#|=yn>#ahi1eMA@Hg`7$w!>k2Yz!q zeY$zj{pX(gCcTqY*J7d)4lo3ROqF9|e4GR+wsXx_F4UF~`S6cPTIhnb^ugZ@>d@}a zV>M85Es3*F@z@QNyKu4ho*cu*R$it=-G@DHY z-c|eJGUbF?#yw5f@BY!^dlY_spYxTebZ5hltqvPC_Ap( z?=hpW$pKh8KH5x-7a8`3><8Hpz75eA{Nhq|^%tgbAmtLo=-VZPYy_#+*bna8 zP9;vY!}k9mvx!g*^35aF>)$07#`GS*=bK~red9rrZvsg2O$I5xdD=vTZ=R;%_6;aG zOxW}lG!Q~$a(Kq1ezo?P7M|oF!@&6T*x&F=ItkcE5kT_Ku3yI~ufV|gOiB0uon+O* zoj8U-7`HJoUfi%L^nW1_n?j)RrX32LfBn;Bf6sbSXD(t^=$n1LgK47YC-V>AALJZW z`0%Tt`1_J-zyFhtyxyps_rGV?mdSs9eBAOd;`HW2Q(fcVc$}JMB>gTSf%iw_ySf`+ zi)J57m>;~3zc^NK&4bPTAFnM}{&VSn=6cx>@y7qg?>}7#J36Oo_61#8A7?F{jf=bQ ze`koF&~x~9=Tt84_Ww8J4%aUDyi&k3Swzfe-l??;Tn|=U;hAYwwdAPQ>%<=Qg8Gdb ziF3XlnZvZA^zsGAQ^t~+kuFP$s$9Q!6w1EZIb*^8NJHz8SCg`J=W$u<);ue9eDGbR z^Q(DTc#{A0_a(wIGiFV&eki-&ydN$xeOXdD!w`?~!sj3|HpAzQwOY2Yb?g%&5K80jGe&L=g(@&Nf zNr$iaT2b4*KF9sF%FmV@UztRKthb^s%7kWmyBSUY+$*2kJG0VxHedLfUHR52cke9! zd1{T$lHiZQG3ifQe@1Hf9C+}#;73K{)9y3xXE|SFIy&*g)fw|0G(T;!K5#X+{q>3| zsT~XqEK`^mpEaq1MtfRC*DOxZ;C!P0KI`AUfNeW3IBdGGS}T8#w277bdV!=*yR=lbbw_I~)5HReQe(ar{Dy@PI^ThkWokTAMs zAr$HsDd+QWEt84@pYj2dl>ztFi&!2NESdRi+cf@FPL~#&N6)uiJo&+%=Sde?wR;*? zFWV=lsLEy5`yhYqCaIcYGMvXp3d6*GD~{5i+jdZB@M?j@-u%MGVyM={?-3i;(O4^#Xl}*PBpBayW`mN zgMWHTQV+!L7P`Ic<(ADy{#?KE+Gc{Q+hKp58%>R|g(p<9H8+0q)-4nD{T_Vdho*3! z=r#Y9rzaY1<1<<0E3w%4>KB`2$%a~<_qpDBI_b-jbnaZfGyTEDN%n!3%|(mf$gzh{ zc9$;mHoJCWW?oT9x8%;Lf+^en=cTefYC3ImqRC>r*Yk>>=UBw*g_f4I>I!hJ?eMAF zKQm*aRlnQw_tDE5R3hTNvqF5h-2S9=t<4v!U;F#M^F>*)o2rwaGPv>IOpQ%Aw>$sl z@A}>)cTO#>f1AdjZ+gD4iLX=S&gT5qu#Fk&zBT)od@}!i?aJ-rJ^rhN3!E-{)c;V= zkKDSzb9#q8>mSD1-|Ibil^=-R-nQDO_wsRunH(1Xa?N&p_^!nEtH?L9)s=I?oze_; zk(ohEUrg<_IWHJz+T^pZ(-CD#*7C_Iow;r!`%C`~9aCm8XWUx&`_!k{+b4e)O7;6y zq!kE!D)H?zo^oaRrnm*4EqTs=iP^zxQaq2(BXw=Zm(HE9PIr1RsvJ}=j{n_tq+~1S zUV##w(_gPuXuK(oU0%IdS#}afr4mDCSmj)f1c&aavidAGT3Y8%yBOulZH`bd3)Wvm#t7zn*@l{AQj8cdgsTx7%je%6!|qA?0}NwH2m%eJyvs z3+!HM{`IVtL|;qNnf*Igu9?aE(xRu({TF*z{JmAA@uBLE5;ROk6#9h`-?7r9A|wKW3M^ef1I&>QL(acKa*mv8!3TN>*MfWt(rdC*pL`r1JIZks3LA?tj!S3z%`F zZdF@&TlrR&$SNtu=Sy_mH~T7i`swK1S+nkIU-skn4(?lPI~KWLZMC+OnKS8!R+;@= z`=Z^EdVl|(S6=fXz~-Q^|xES3|z`5dlt zzch|}eehhZ`~!*8zmMKDy~}HIXWjBoJ_gTQ^$g^nE9(2!EfHB>Z#(0aLF6r=9ZFf< ztG~|83^;ki=kdnZY~6?AV!kS=@32a|%Qmg;ctn?1QLV&6r}Fr>hh!hNsHZPTX>=@z zvvWw6T3!2K(f<2-8}IHtxFGoMk&G2#XF?8CHfkI{)sQf|FDj3LM}vWZ0aWXN$G#KR zxh+`qM39d|sf*d|0Aum;6oxbh2G&0e3{0I2jL*R1-zQyONIE#EtoeO+?OM@5Ar^1B z>%#vOT1HF;&*qGen zEkE}&P&w`e9z9j2w$jpX3O3uQ?8!<6bH`CLLRZdhjUfQnv-?i_an$QWu&+Gh!-rw4+HoYl)%`u%-O#KTN{8N}4 z{$0dKSG@F;_L}m97Qv#4s#g*}e-C6X6zp?8ddx|Xnb9mySYz^~C(`pDAG!AS= zXY)*2A@5YbplI5r1>&>3M5_9xwu|N0oJd<<&oCD&u~8`c&qVh(L^ zkMLZ&>9W8AnK!-+kAMBTowY`Br(vz~9z~H`j+QA~riD&r<7#O#Rh{?Y{Vpd)&aDp^ zrvK`S@iN~uab=u2$NKJR7Xo)rs5X+7TzY+~RoQC2eZtcnA`8D69bUIEjBknC21$1l zm;d#WT{ z-q4Ltx3Jsrjhku;n12}&)WGKRxh=GyE$$3`KI-(#;qJx z>&}UFH*fx6;Z@(O)jRQG%jvl*wu@ONY8*Z1k-KlYUu)1Z{?|JVAE}mf912a@zn<^0 z@i8YBewGKE98;3A?DBFmn$zASOgE_Au*6yC%Cl#GyB{(9IY{I_KD{cC4V$8p_K zvtYOT7A$jShXU)OzRgt^J=p3Kyr!F#9hl-!JLA+fmTi+Ak{76Ko4m?euYA#a=EpV< z1@6h;jraetjKlq8)BAr8Uo@UF>`tqyyMFK$m)|n`*p8%N+4B8UzW;l(Nv<_b=D?SF z1Cjhy4xIBM^k;Q2y?Up2XWL4%ruf92ZR?cXKW8!pJ}7on4l}oEjq-dg$6>dcmya`R zu4m`9+q3%oLhsu@<~&eg`?oM5s(tnEvb@F}IpKG{=}e6HUwiv$(Yq^CuSoB&b=lN) z_Kf(1$wyyLeE8@`NB%;a5+-Ar6T5QH_hh&1{U&jD>iiwf%9_m#?$$fp?JqD)4=Xg^ zZTYD(RJc(d81O?R>b1Y}2 zyERp;cq;kwy3vOXKlU{)ch{CaIAhbn?B?`ETOVq4s_vg9qPpt)Th9${Y`ZqS{5hfi z)BP>-GZqSN`M%>7+kppa&MigyPV&p=oW9ZXbIF{o@8`O#{CM+jVSUu&kUb1P-u`+q z<-^5!il>EleaH;I{N{7T96xTw=MR^CPzSX zlBe(T4wcQE8Grq?KUA&udY^eOoBde+zXYv>g=_Zy_Iqy@#ATcnZS=?1^wKpBt;qCJ zm!}4y38%W4WmkuM{V9BG_k%Fo_A9Z;U#IlDN!OQ7y7n@5 zfxpy@DAgxgO??70t@_d@TyIS}82#IG(LXExMWIROtr%wPS~4|zdzH!Z*js}2e^g5Y zx^f~nZt7cpb~el1^}!KN*De@$$@j!OpMEJJY)a6*-RpK-DAl`|H&;ZT2Lr{I=K3T%VvYVSV_E2u6Jwf#ugcL^_?e~*om zIvQD-nHtvo;oX0l^}==^#+mHXIX_Wzwcs~g*Y8yDVq@O;~ytP5dn-b|l*r<}MWv{P4w`QFE{m8usP_#dcG zlHZua8Ji+>Qc3Y;UR`n3oCblpip!UKC02hjNbs^Z6uxlHqul9)-0a{kP6dXQ3okQh zCNMBfV_@J9%gxU(V_;y&$t}xBVL$=QFBllaRxmI!fDl545y1qr(D)8uL7W5=0|Q?} zNl{`p0|P@$NjBI{W+c^6)5KOFi9wi983=~~%~1>tAbS~g7#Kj9fq_9KHKnAOfq_9L zHK(`)s)rHm9|jJnUfv(MDVd3&)xt6-6oS)y%d049x+GD{S66+HBe%uMw?T*I6d zlJiqilNEFoobwARi!#$QN)(I?42^Y-3=Az4G?FzHd`ojmGL!R*Qp*#IQgswO^FWF- zN=gc>^z}gw)5|YPSI||+FDS{(&nvc4NG>SNOtCdIurM+& zTLT5Kp}LtV$+iXx8JX!Bx+R&ZMYe_tr5P!@Nh!7l3Pq`Dw#FcLB1nIQ*t2AE-nD62YDbfucRnZw@a4TMLk?qSWLP5S^G?ppctbp<4o#G&WKwDb)qXiY_E74WR->iEx3O z%-qZp-I7vJ9HitILZjb6A*C`eF*h?=w86x|IT^XB3gxMw90uk>n2@xSmzYyom8x4@l&o8jnOv-plA4s0pPX%LU}d0C zoR|-?zBn--oF1W43PmZl#tOx$IjPAdnPsWE#fka0CJL#EMLCta;J_#XlX>~NX^F)p z5RZb40f!V=48%(=)y>H-F3w3UE>=iFC;~-&Nl^;app=5tyu_T6N?QYkg5pY0R2YGx z0>aKOghxzK3Mh?a=7IAG+}Px#!h-y?G*G%rE`l?Ql5LZV(iD=5(rk?^^b8aVlk;;6 zYz_3x3=|3ric3-pY)ur3OAhyy;*z4&y!4U`TSJh8lS@mAQd1SG^7B%4lk-dSN+5}&xTGjExkNWP zsYszP9g?1njX;4@l&qT$N;C?E1-Xe8wq}+fMrIyFS8{${afxnOQkg<=A}E#Rm!%dJ zCnthrK^12{xXdWAHC2G54FiSb{G9xvf}+e^TO);%qQt!7wA3P7BZzQrVo6bE1&EhX zl%JcJlMgDOQZkDRauO?b%QN#*^2==v6q0jtYz=e_z&S6cG#8Z2LHP{KG%_^>6`Mx7 zAd!OnWZj(NBwGuGveHc5lFZypa8ggp2lF$EK$##dUjbSkfD?CqL5Xf*fo?%TG05j2 zUQueAZcb)#iEa)!2w_sWpxQvUAhEbu7p$tFSOHdgKuaJ!15lUS!7N^2X4!Ce}Vp%FKxt#n&P@+ZFSDc)fld7ARm|T(%ir1plw9LFz zNY(%qPH--`Y=O$=mf0GD>JE^~aY-?#)eB;S#6j4tIHjZjB%mN5 zAR!?EC*TaIkc5DM00_egsA2&D2?hqn1<42-jUX*h4v?XY3=9%bng>KPfe45^*hNIh zi-5E+GJx9U(3UxA`YE*^>~2PA2qL*d0Gqp*5bh*Po(kcC)n1w6lHz2LgCSuD?y^}y z*?)>limRYhm!$F!eGpF!wMpFhnpgFf3(YU=S%TNzMm(8X6Wl z3=9lOr70z#eqU5>DwqaIDh`%K!iX literal 0 HcmV?d00001 diff --git a/test/files/test-300x300.mp4 b/test/files/mp4/test-300x300.mp4 similarity index 100% rename from test/files/test-300x300.mp4 rename to test/files/mp4/test-300x300.mp4 diff --git a/test/files/test-audio.m4v b/test/files/mp4/test-audio.m4v similarity index 100% rename from test/files/test-audio.m4v rename to test/files/mp4/test-audio.m4v diff --git a/test/files/test-audio.mp4 b/test/files/mp4/test-audio.mp4 similarity index 100% rename from test/files/test-audio.mp4 rename to test/files/mp4/test-audio.mp4 diff --git a/test/files/test-iso5.mp4 b/test/files/mp4/test-iso5.mp4 similarity index 100% rename from test/files/test-iso5.mp4 rename to test/files/mp4/test-iso5.mp4 diff --git a/test/functional/uploads_controller_test.rb b/test/functional/uploads_controller_test.rb index 82aa5efd5..7babf6f11 100644 --- a/test/functional/uploads_controller_test.rb +++ b/test/functional/uploads_controller_test.rb @@ -241,6 +241,16 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest create_upload!("test/files/webm/test-512x512.mkv", user: @user) assert_match("File type is not supported", Upload.last.error) end + + should "fail for a .mp4 file encoded with h265" do + create_upload!("test/files/mp4/test-300x300.h265.mp4", user: @user) + assert_match("File type is not supported", Upload.last.error) + end + + should "fail for a .mp4 file encoded with av1" do + create_upload!("test/files/mp4/test-300x300.av1.mp4", user: @user) + assert_match("File type is not supported", Upload.last.error) + end end context "for a video longer than the video length limit" do @@ -334,10 +344,11 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest should_upload_successfully("test/files/test.png") should_upload_successfully("test/files/test-static-32x32.gif") should_upload_successfully("test/files/test-animated-86x52.gif") - should_upload_successfully("test/files/test-300x300.mp4") - should_upload_successfully("test/files/test-audio.mp4") + should_upload_successfully("test/files/mp4/test-300x300.mp4") + should_upload_successfully("test/files/mp4/test-300x300.vp9.mp4") + should_upload_successfully("test/files/mp4/test-audio.mp4") + should_upload_successfully("test/files/mp4/test-audio.m4v") should_upload_successfully("test/files/webm/test-512x512.webm") - should_upload_successfully("test/files/test-audio.m4v") # should_upload_successfully("test/files/compressed.swf") should_upload_successfully("test/files/avif/fox.profile0.8bpc.yuv420.monochrome.avif") diff --git a/test/unit/media_file_test.rb b/test/unit/media_file_test.rb index 2e2e2c604..0e316409c 100644 --- a/test/unit/media_file_test.rb +++ b/test/unit/media_file_test.rb @@ -36,7 +36,7 @@ class MediaFileTest < ActiveSupport::TestCase should "determine the correct dimensions for a mp4 file" do skip unless MediaFile.videos_enabled? - assert_equal([300, 300], MediaFile.open("test/files/test-300x300.mp4").dimensions) + assert_equal([300, 300], MediaFile.open("test/files/mp4/test-300x300.mp4").dimensions) end should "determine the correct dimensions for a ugoira file" do @@ -110,15 +110,15 @@ class MediaFileTest < ActiveSupport::TestCase end should "determine the correct extension for a mp4 file" do - assert_equal(:mp4, MediaFile.open("test/files/test-300x300.mp4").file_ext) + assert_equal(:mp4, MediaFile.open("test/files/mp4/test-300x300.mp4").file_ext) end should "determine the correct extension for a m4v file" do - assert_equal(:mp4, MediaFile.open("test/files/test-audio.m4v").file_ext) + assert_equal(:mp4, MediaFile.open("test/files/mp4/test-audio.m4v").file_ext) end should "determine the correct extension for an iso5 mp4 file" do - assert_equal(:mp4, MediaFile.open("test/files/test-iso5.mp4").file_ext) + assert_equal(:mp4, MediaFile.open("test/files/mp4/test-iso5.mp4").file_ext) end should "determine the correct extension for a ugoira file" do @@ -162,7 +162,7 @@ class MediaFileTest < ActiveSupport::TestCase should "generate a preview image for a video" do skip unless MediaFile.videos_enabled? assert_equal([150, 150], MediaFile.open("test/files/webm/test-512x512.webm").preview(150, 150).dimensions) - assert_equal([150, 150], MediaFile.open("test/files/test-300x300.mp4").preview(150, 150).dimensions) + assert_equal([150, 150], MediaFile.open("test/files/mp4/test-300x300.mp4").preview(150, 150).dimensions) end should "be able to fit to width only" do @@ -196,18 +196,18 @@ class MediaFileTest < ActiveSupport::TestCase context "for an mp4 file " do should "detect videos with audio" do - assert_equal(true, MediaFile.open("test/files/test-audio.mp4").has_audio?) - assert_equal(false, MediaFile.open("test/files/test-300x300.mp4").has_audio?) + assert_equal(true, MediaFile.open("test/files/mp4/test-audio.mp4").has_audio?) + assert_equal(false, MediaFile.open("test/files/mp4/test-300x300.mp4").has_audio?) end should "determine the duration of the video" do - file = MediaFile.open("test/files/test-audio.mp4") + file = MediaFile.open("test/files/mp4/test-audio.mp4") assert_equal(false, file.is_corrupt?) assert_equal(1.002667, file.duration) assert_equal(10/1.002667, file.frame_rate) assert_equal(10, file.frame_count) - file = MediaFile.open("test/files/test-300x300.mp4") + file = MediaFile.open("test/files/mp4/test-300x300.mp4") assert_equal(false, file.is_corrupt?) assert_equal(5.7, file.duration) assert_equal(1.75, file.frame_rate.round(2)) @@ -215,9 +215,15 @@ class MediaFileTest < ActiveSupport::TestCase end should "detect corrupt videos" do - file = MediaFile.open("test/files/mp4/test-corrupt.mp4") + assert_equal(true, MediaFile.open("test/files/mp4/test-corrupt.mp4").is_corrupt?) + end - assert_equal(true, file.is_corrupt?) + should "detect supported files" do + assert_equal(true, MediaFile.open("test/files/mp4/test-300x300.mp4").is_supported?) + assert_equal(true, MediaFile.open("test/files/mp4/test-300x300.vp9.mp4").is_supported?) + + assert_equal(false, MediaFile.open("test/files/mp4/test-300x300.h265.mp4").is_supported?) + assert_equal(false, MediaFile.open("test/files/mp4/test-300x300.av1.mp4").is_supported?) end end