Class: ActiveStorage::HotCell::Server::Analyzers::Media::Ffprobe
- Inherits:
-
ToolOperation
- Object
- HotCell::Operation
- Operation
- ToolOperation
- ActiveStorage::HotCell::Server::Analyzers::Media::Ffprobe
- Defined in:
- lib/active_storage/hot_cell/server/analyzers/media/ffprobe.rb
Overview
What Rails' video and audio analyzers do, moved out of the application: ffprobe, and the numbers it
reports shaped exactly as Analyzer::VideoAnalyzer and Analyzer::AudioAnalyzer shape them. One
operation serves both client analyzers, because ffprobe reports video and audio streams in one pass.
This reads its tool's stdout, and that is a judgement, so here it is.
ffprobe parses the media in an exec'd child that dies at the end of the call. What comes back into this
worker is JSON on a bounded buffer, parsed by JSON.parse — not a media decoder, and not a parser with a
history of memory-safety bugs. The question that decides whether recycling a worker is safe is whether a
malicious input can execute in this process, and through ffprobe's JSON it cannot. Reading a tool's
output file with an in-process media library is in-process decoding; parsing its structured stdout with
the standard library is not.
The result is a superset of what either analyzer writes, and the client analyzers slice it to Rails'
exact keys — the same split the image analyzer uses. Two things here are deliberately not Rails. Every
number is coerced tolerantly rather than with Integer()/Float() that raise, because ffprobe's output
is attacker-controlled where Rails' is trusted; a value that is not a clean number is dropped rather than
crashing the analysis. And tags — title, artist, arbitrary bytes that need not be valid UTF-8 — are
never returned, where Rails writes them straight into the database.
Constant Summary collapse
- ROTATIONS =
[ 90, 270, -90, -270 ].freeze
Constants inherited from Operation
Instance Method Summary collapse
-
#perform(inputs, _outputs, probe_arguments: []) ⇒ Object
probe_argumentsisconfig.active_storage.ffprobe_arguments, split, and it goes where Rails puts it: before the input path, which is where an input option such as-codec_whitelisthas to be to take effect.
Instance Method Details
#perform(inputs, _outputs, probe_arguments: []) ⇒ Object
probe_arguments is config.active_storage.ffprobe_arguments, split, and it goes where Rails
puts it: before the input path, which is where an input option such as -codec_whitelist has
to be to take effect.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/active_storage/hot_cell/server/analyzers/media/ffprobe.rb', line 40 def perform(inputs, _outputs, probe_arguments: []) source, = inputs probed = JSON.parse(run!("ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", *arguments!(:probe_arguments, probe_arguments), source.fd_path, pass: [ source.to_io ]).out) video = stream_of(probed, "video") audio = stream_of(probed, "audio") { duration: duration(probed, video, audio), bytes: source.to_io.stat.size, video: !video.nil?, audio: !audio.nil? } .merge(video ? (video) : {}) .merge(audio ? (audio) : {}) .compact rescue JSON::ParserError => error raise UnreadableDocument, "ffprobe said something that is not JSON: #{error.[0, 120]}" end |