uploads: disallow more video formats not supported by all browsers.

Disallow uploading videos with 10-bit color or 4:4:4 chroma subsampling.
Neither of these features are supported by Firefox.

Only 8 such videos have been uploaded to Danbooru:

* https://danbooru.donmai.us/media_assets/3070695 (4:4:4)
* https://danbooru.donmai.us/media_assets/3070697 (4:4:4)
* https://danbooru.donmai.us/media_assets/3292518 (4:4:4)
* https://danbooru.donmai.us/media_assets/3358659 (10-bit)
* https://danbooru.donmai.us/media_assets/3358660 (10-bit)
* https://danbooru.donmai.us/media_assets/3730866 (10-bit)
* https://danbooru.donmai.us/media_assets/5056665 (10-bit)
* https://danbooru.donmai.us/media_assets/5479605 (4:4:4)

Note that Exiftool doesn't output this information, so it's not in the
EXIF metadata. We have to reply on ffprobe at upload time instead.

Followup to #3615.
This commit is contained in:
evazion
2022-10-28 01:21:34 -05:00
parent 57316dc622
commit 6e685cdd42
15 changed files with 81 additions and 11 deletions

View File

@@ -83,6 +83,10 @@ class FFmpeg
frame_count / duration
end
def pix_fmt
video_stream[:pix_fmt]
end
def video_codec
video_stream[:codec_name]
end

View File

@@ -5,7 +5,7 @@
#
# @see https://github.com/streamio/streamio-ffmpeg
class MediaFile::Video < MediaFile
delegate :duration, :frame_count, :frame_rate, :has_audio?, :video_codec, :video_stream, :video_streams, :audio_streams, to: :video
delegate :duration, :frame_count, :frame_rate, :has_audio?, :pix_fmt, :video_codec, :video_stream, :video_streams, :audio_streams, to: :video
def dimensions
[video.width, video.height]
@@ -21,6 +21,17 @@ class MediaFile::Video < MediaFile
return false if is_webm? && metadata["Matroska:DocType"] != "webm"
return false if is_mp4? && !video_codec.in?(["h264", "vp9"])
# Only allow pixel formats supported by most browsers. Don't allow 10-bit video or 4:4:4 subsampling (neither are supported by Firefox).
#
# yuv420p: 8-bit YUV, 4:2:0 subsampling. The vast majority of videos use this format.
# yuvj420p: 8-bit YUV, 4:2:0 subsampling, color range restricted to 16-235. Uncommon, but widely supported.
# yuv444p: 8-bit YUV, 4:4:4 subsampling (i.e. no subsampling). Uncommon, not supported by Firefox.
# yuv420p10le: 10-bit YUV, 4:2:0 subsampling (i.e. 10-bit video). Uncommon, not supported by Firefox.
# gbrp: 8-bit RGB (used by VP9). Uncommon, but widely supported.
#
# https://github.com/FFmpeg/FFmpeg/blob/master/libavutil/pixfmt.h
return false if !pix_fmt.in?(%w[yuv420p yuvj420p gbrp])
true
end

6
test/files/mp4/README.md Normal file
View File

@@ -0,0 +1,6 @@
Test file sources:
* https://danbooru.donmai.us/posts/2878908 (https://twitter.com/chan_co/status/913025965604749314)
* https://danbooru.donmai.us/posts/4270949 (https://twitter.com/chrone_co/status/1342709028401643520)
* https://danbooru.donmai.us/posts/5152189 (https://twitter.com/001_31_/status/1491405055563792386)
* https://github.com/jursonovicst/gradient/blob/master/player_validation_sequences/test_yuv420p10le_x265.mp4

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -243,12 +243,37 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest
end
should "fail for a .mp4 file encoded with h265" do
create_upload!("test/files/mp4/test-300x300.h265.mp4", user: @user)
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)
create_upload!("test/files/mp4/test-300x300-av1.mp4", user: @user)
assert_match("File type is not supported", Upload.last.error)
end
should "fail for a 10-bit color .mp4 file encoded with av1" do
create_upload!("test/files/mp4/test-yuv420p10le-av1.mp4", user: @user)
assert_match("File type is not supported", Upload.last.error)
end
should "fail for a 10-bit color .mp4 file encoded with h264" do
create_upload!("test/files/mp4/test-yuv420p10le-h264.mp4", user: @user)
assert_match("File type is not supported", Upload.last.error)
end
should "fail for a 10-bit color .mp4 file encoded with vp9" do
create_upload!("test/files/mp4/test-yuv420p10le-vp9.mp4", user: @user)
assert_match("File type is not supported", Upload.last.error)
end
should "fail for a 4:4:4 subsampled .mp4 file" do
create_upload!("test/files/mp4/test-300x300-yuv444p-h264.mp4", user: @user)
assert_match("File type is not supported", Upload.last.error)
end
should "fail for a 10-bit color .webm file encoded with vp9" do
create_upload!("test/files/webm/test-yuv420p10le-vp9.webm", user: @user)
assert_match("File type is not supported", Upload.last.error)
end
end
@@ -335,7 +360,7 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest
assert_equal([550, 368], full_variant.dimensions)
assert_equal(:jpg, full_variant.file_ext)
assert_equal(nil, media_asset.variant(:sample))
assert_nil(media_asset.variant(:sample))
end
end
@@ -345,10 +370,12 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest
should_upload_successfully("test/files/test-static-32x32.gif")
should_upload_successfully("test/files/test-animated-86x52.gif")
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-300x300-vp9.mp4")
should_upload_successfully("test/files/mp4/test-300x300-yuvj420p-h264.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/webm/test-gbrp-vp9.webm")
# should_upload_successfully("test/files/compressed.swf")
should_upload_successfully("test/files/avif/fox.profile0.8bpc.yuv420.monochrome.avif")

View File

@@ -214,16 +214,36 @@ class MediaFileTest < ActiveSupport::TestCase
assert_equal(10, file.frame_count)
end
should "determine the pixel format of the video" do
assert_equal("yuv420p", MediaFile.open("test/files/mp4/test-300x300-av1.mp4").pix_fmt)
assert_equal("yuv420p", MediaFile.open("test/files/mp4/test-300x300-h265.mp4").pix_fmt)
assert_equal("yuv420p", MediaFile.open("test/files/mp4/test-300x300-vp9.mp4").pix_fmt)
assert_equal("yuv420p", MediaFile.open("test/files/mp4/test-300x300.mp4").pix_fmt)
assert_equal("yuv420p", MediaFile.open("test/files/mp4/test-audio.m4v").pix_fmt)
assert_equal("yuv420p", MediaFile.open("test/files/mp4/test-audio.mp4").pix_fmt)
assert_equal("yuv420p", MediaFile.open("test/files/mp4/test-iso5.mp4").pix_fmt)
assert_equal("yuv444p", MediaFile.open("test/files/mp4/test-300x300-yuv444p-h264.mp4").pix_fmt)
assert_equal("yuvj420p", MediaFile.open("test/files/mp4/test-300x300-yuvj420p-h264.mp4").pix_fmt)
assert_equal("yuv420p10le", MediaFile.open("test/files/mp4/test-yuv420p10le-av1.mp4").pix_fmt)
assert_equal("yuv420p10le", MediaFile.open("test/files/mp4/test-yuv420p10le-h264.mp4").pix_fmt)
assert_equal("yuv420p10le", MediaFile.open("test/files/mp4/test-yuv420p10le-vp9.mp4").pix_fmt)
end
should "detect corrupt videos" do
assert_equal(true, MediaFile.open("test/files/mp4/test-corrupt.mp4").is_corrupt?)
end
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(true, MediaFile.open("test/files/mp4/test-300x300-vp9.mp4").is_supported?)
assert_equal(true, MediaFile.open("test/files/mp4/test-300x300-yuvj420p-h264.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?)
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?)
assert_equal(false, MediaFile.open("test/files/mp4/test-300x300-yuv444p-h264.mp4").is_supported?)
assert_equal(false, MediaFile.open("test/files/mp4/test-yuv420p10le-av1.mp4").is_supported?)
assert_equal(false, MediaFile.open("test/files/mp4/test-yuv420p10le-h264.mp4").is_supported?)
assert_equal(false, MediaFile.open("test/files/mp4/test-yuv420p10le-vp9.mp4").is_supported?)
end
end
@@ -235,10 +255,12 @@ class MediaFileTest < ActiveSupport::TestCase
assert_equal(10, file.frame_count)
end
should "not detect .mkv files as .webm" do
file = MediaFile.open("test/files/webm/test-512x512.mkv")
should "detect supported files" do
assert_equal(true, MediaFile.open("test/files/webm/test-512x512.webm").is_supported?)
assert_equal(true, MediaFile.open("test/files/webm/test-gbrp-vp9.webm").is_supported?)
assert_equal(false, file.is_supported?)
assert_equal(false, MediaFile.open("test/files/webm/test-512x512.mkv").is_supported?)
assert_equal(false, MediaFile.open("test/files/webm/test-yuv420p10le-vp9.webm").is_supported?)
end
end