Fix some media attachments being converted with too high framerates (#17619)
Video files with variable framerates are converted to constant framerate videos and the output framerate picked by ffmpeg is based on the original file's container framerate (which can be different from the average framerate). This means that an input video with variable framerate with about 30 frames per second on average, but a maximum of 120 fps will be converted to a constant 120 fps file, which won't be processed by other Mastodon servers. This commit changes it so that input files with VFR and a maximum framerate above the framerate threshold are converted to VFR files with the maximum frame rate enforced.
This commit is contained in:
parent
51e67f3243
commit
166f6e4b50
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
class VideoMetadataExtractor
|
class VideoMetadataExtractor
|
||||||
attr_reader :duration, :bitrate, :video_codec, :audio_codec,
|
attr_reader :duration, :bitrate, :video_codec, :audio_codec,
|
||||||
:colorspace, :width, :height, :frame_rate
|
:colorspace, :width, :height, :frame_rate, :r_frame_rate
|
||||||
|
|
||||||
def initialize(path)
|
def initialize(path)
|
||||||
@path = path
|
@path = path
|
||||||
|
@ -42,6 +42,7 @@ class VideoMetadataExtractor
|
||||||
@width = video_stream[:width]
|
@width = video_stream[:width]
|
||||||
@height = video_stream[:height]
|
@height = video_stream[:height]
|
||||||
@frame_rate = video_stream[:avg_frame_rate] == '0/0' ? nil : Rational(video_stream[:avg_frame_rate])
|
@frame_rate = video_stream[:avg_frame_rate] == '0/0' ? nil : Rational(video_stream[:avg_frame_rate])
|
||||||
|
@r_frame_rate = video_stream[:r_frame_rate] == '0/0' ? nil : Rational(video_stream[:r_frame_rate])
|
||||||
end
|
end
|
||||||
|
|
||||||
if (audio_stream = audio_streams.first)
|
if (audio_stream = audio_streams.first)
|
||||||
|
|
|
@ -38,6 +38,12 @@ class MediaAttachment < ApplicationRecord
|
||||||
|
|
||||||
MAX_DESCRIPTION_LENGTH = 1_500
|
MAX_DESCRIPTION_LENGTH = 1_500
|
||||||
|
|
||||||
|
IMAGE_LIMIT = 10.megabytes
|
||||||
|
VIDEO_LIMIT = 40.megabytes
|
||||||
|
|
||||||
|
MAX_VIDEO_MATRIX_LIMIT = 2_304_000 # 1920x1200px
|
||||||
|
MAX_VIDEO_FRAME_RATE = 60
|
||||||
|
|
||||||
IMAGE_FILE_EXTENSIONS = %w(.jpg .jpeg .png .gif).freeze
|
IMAGE_FILE_EXTENSIONS = %w(.jpg .jpeg .png .gif).freeze
|
||||||
VIDEO_FILE_EXTENSIONS = %w(.webm .mp4 .m4v .mov).freeze
|
VIDEO_FILE_EXTENSIONS = %w(.webm .mp4 .m4v .mov).freeze
|
||||||
AUDIO_FILE_EXTENSIONS = %w(.ogg .oga .mp3 .wav .flac .opus .aac .m4a .3gp .wma).freeze
|
AUDIO_FILE_EXTENSIONS = %w(.ogg .oga .mp3 .wav .flac .opus .aac .m4a .3gp .wma).freeze
|
||||||
|
@ -75,6 +81,7 @@ class MediaAttachment < ApplicationRecord
|
||||||
VIDEO_FORMAT = {
|
VIDEO_FORMAT = {
|
||||||
format: 'mp4',
|
format: 'mp4',
|
||||||
content_type: 'video/mp4',
|
content_type: 'video/mp4',
|
||||||
|
vfr_frame_rate_threshold: MAX_VIDEO_FRAME_RATE,
|
||||||
convert_options: {
|
convert_options: {
|
||||||
output: {
|
output: {
|
||||||
'loglevel' => 'fatal',
|
'loglevel' => 'fatal',
|
||||||
|
@ -152,12 +159,6 @@ class MediaAttachment < ApplicationRecord
|
||||||
all: '-quality 90 -strip +set modify-date +set create-date',
|
all: '-quality 90 -strip +set modify-date +set create-date',
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
IMAGE_LIMIT = 10.megabytes
|
|
||||||
VIDEO_LIMIT = 40.megabytes
|
|
||||||
|
|
||||||
MAX_VIDEO_MATRIX_LIMIT = 2_304_000 # 1920x1200px
|
|
||||||
MAX_VIDEO_FRAME_RATE = 60
|
|
||||||
|
|
||||||
belongs_to :account, inverse_of: :media_attachments, optional: true
|
belongs_to :account, inverse_of: :media_attachments, optional: true
|
||||||
belongs_to :status, inverse_of: :media_attachments, optional: true
|
belongs_to :status, inverse_of: :media_attachments, optional: true
|
||||||
belongs_to :scheduled_status, inverse_of: :media_attachments, optional: true
|
belongs_to :scheduled_status, inverse_of: :media_attachments, optional: true
|
||||||
|
|
|
@ -13,6 +13,7 @@ module Paperclip
|
||||||
@time = options[:time] || 3
|
@time = options[:time] || 3
|
||||||
@passthrough_options = options[:passthrough_options]
|
@passthrough_options = options[:passthrough_options]
|
||||||
@convert_options = options[:convert_options].dup
|
@convert_options = options[:convert_options].dup
|
||||||
|
@vfr_threshold = options[:vfr_frame_rate_threshold]
|
||||||
end
|
end
|
||||||
|
|
||||||
def make
|
def make
|
||||||
|
@ -41,6 +42,11 @@ module Paperclip
|
||||||
when 'mp4'
|
when 'mp4'
|
||||||
@output_options['acodec'] = 'aac'
|
@output_options['acodec'] = 'aac'
|
||||||
@output_options['strict'] = 'experimental'
|
@output_options['strict'] = 'experimental'
|
||||||
|
|
||||||
|
if high_vfr?(metadata) && !eligible_to_passthrough?(metadata)
|
||||||
|
@output_options['vsync'] = 'vfr'
|
||||||
|
@output_options['r'] = @vfr_threshold
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
command_arguments, interpolations = prepare_command(destination)
|
command_arguments, interpolations = prepare_command(destination)
|
||||||
|
@ -88,13 +94,21 @@ module Paperclip
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_options_from_metadata(metadata)
|
def update_options_from_metadata(metadata)
|
||||||
return unless @passthrough_options && @passthrough_options[:video_codecs].include?(metadata.video_codec) && @passthrough_options[:audio_codecs].include?(metadata.audio_codec) && @passthrough_options[:colorspaces].include?(metadata.colorspace)
|
return unless eligible_to_passthrough?(metadata)
|
||||||
|
|
||||||
@format = @passthrough_options[:options][:format] || @format
|
@format = @passthrough_options[:options][:format] || @format
|
||||||
@time = @passthrough_options[:options][:time] || @time
|
@time = @passthrough_options[:options][:time] || @time
|
||||||
@convert_options = @passthrough_options[:options][:convert_options].dup
|
@convert_options = @passthrough_options[:options][:convert_options].dup
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def high_vfr?(metadata)
|
||||||
|
@vfr_threshold && metadata.r_frame_rate && metadata.r_frame_rate > @vfr_threshold
|
||||||
|
end
|
||||||
|
|
||||||
|
def eligible_to_passthrough?(metadata)
|
||||||
|
@passthrough_options && @passthrough_options[:video_codecs].include?(metadata.video_codec) && @passthrough_options[:audio_codecs].include?(metadata.audio_codec) && @passthrough_options[:colorspaces].include?(metadata.colorspace)
|
||||||
|
end
|
||||||
|
|
||||||
def update_attachment_type(metadata)
|
def update_attachment_type(metadata)
|
||||||
@attachment.instance.type = MediaAttachment.types[:gifv] unless metadata.audio_codec
|
@attachment.instance.type = MediaAttachment.types[:gifv] unless metadata.audio_codec
|
||||||
end
|
end
|
||||||
|
|
Reference in New Issue