media assets: track corrupted files in media metadata.

If a media asset is corrupt, include the error message from libvips or
ffmpeg in the "Vips:Error" or "FFmpeg:Error" fields in the media
metadata table.

Corrupt files can't be uploaded nowadays, but they could be in the past,
so we have some old corrupted files that we can't generate thumbnails
for. This lets us mark these files in the metadata so they're findable
with the tag search `exif:Vips:Error`.

Known bug: Vips has a single global error buffer that is shared between
threads and that isn't cleared between operations. So we can't reliably
get the actual error message because it may pick up errors from other
threads, or from previous operations in the same thread.
This commit is contained in:
evazion
2022-11-02 14:55:07 -05:00
parent 19c091d81c
commit 3172031caa
7 changed files with 68 additions and 33 deletions

View File

@@ -28,10 +28,22 @@ class MediaFile::Image < MediaFile
end
def is_corrupt?
error.present?
end
def error
image = Vips::Image.new_from_file(file.path, fail: true)
image.stats
false
rescue Vips::Error
true
nil
rescue Vips::Error => e
# XXX Vips has a single global error buffer that is shared between threads and that isn't cleared between operations.
# We can't reliably use `e.message` here because it may pick up errors from other threads, or from previous
# operations in the same thread.
"libvips error"
end
def metadata
super.merge({ "Vips:Error" => error }.compact_blank)
end
def duration
@@ -44,7 +56,7 @@ class MediaFile::Image < MediaFile
when :gif, :webp
n_pages
when :png
metadata.fetch("PNG:AnimationFrames", 1)
exif_metadata.fetch("PNG:AnimationFrames", 1)
when :avif
video.frame_count
else
@@ -156,12 +168,12 @@ class MediaFile::Image < MediaFile
# @return [Vips::Image] the Vips image object for the file
def image
Vips::Image.new_from_file(file.path, fail: strict).autorot
Vips::Image.new_from_file(file.path, fail: false).autorot
end
def video
FFmpeg.new(file)
end
memoize :image, :video, :dimensions, :is_corrupt?, :is_animated_gif?, :is_animated_png?
memoize :image, :video, :preview_frame, :dimensions, :error, :metadata, :is_corrupt?, :is_animated_gif?, :is_animated_png?
end

View File

@@ -108,5 +108,5 @@ class MediaFile::Ugoira < MediaFile
FFmpeg.new(convert).smart_video_preview
end
memoize :zipfile, :preview_frame, :dimensions, :convert
memoize :zipfile, :preview_frame, :dimensions, :convert, :metadata
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?, :pix_fmt, :video_codec, :video_stream, :video_streams, :audio_streams, to: :video
delegate :duration, :frame_count, :frame_rate, :has_audio?, :is_corrupt?, :pix_fmt, :video_codec, :video_stream, :video_streams, :audio_streams, :error, to: :video
def dimensions
[video.width, video.height]
@@ -15,10 +15,14 @@ class MediaFile::Video < MediaFile
preview_frame.preview!(max_width, max_height, **options)
end
def metadata
super.merge({ "FFmpeg:Error" => error }.compact_blank)
end
def is_supported?
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_webm? && exif_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).
@@ -35,11 +39,6 @@ class MediaFile::Video < MediaFile
true
end
# True if decoding the video fails.
def is_corrupt?
video.playback_info.blank?
end
private
def video
@@ -50,5 +49,5 @@ class MediaFile::Video < MediaFile
video.smart_video_preview
end
memoize :video, :preview_frame, :dimensions, :duration, :has_audio?
memoize :video, :preview_frame, :dimensions, :metadata, :duration, :has_audio?
end