Success! The key was to use option_vf/1
, not option_filter_complex/1
(and it accepts a string as argument).
Full command as follows
def get_video_framecount(file_path) do
case FFprobe.streams(file_path) do
{:ok, streams} ->
streams
|> Enum.find(fn stream -> stream["codec_type"] == "video" end)
|> case do
nil -> {:error, "No video stream found"}
video_stream ->
nb_frames =
video_stream
|> Map.get("nb_frames", %{})
case nb_frames do
nil -> {:error, "nb_frames not found"}
%{} -> {:error, "nb_frames not found. (empty map)"}
nb_frames ->
case Integer.parse(nb_frames) do
{number, _} -> {:ok, number}
end
end
end
{:error, reason} -> {:error, reason}
end
end
def create_thumbnail(input_file, output_file) do
case get_video_framecount(input_file) do
{:error, reason} -> {:error, reason}
{:ok, framecount} ->
frame_interval = div(framecount, 25)
scale_width = 160
tile_grid = "5x5"
# ffmpeg -y -i ~/Videos/moose-encounter_75.mp4 -frames:v 1 -vf 'select=not(mod(n\,257)),scale=160:-1,tile=5x5' -update 1 -fps_mode passthrough ~/Videos/thumb.jpg
command =
FFmpex.new_command
|> add_global_option(option_y())
|> add_input_file(input_file)
|> add_output_file(output_file)
|> add_file_option(option_vframes(1))
|> add_file_option(option_vf("select=not(mod(n\\,#{frame_interval})),scale=#{scale_width}:-1,tile=#{tile_grid}"))
|> add_file_option(option_vsync(1)) # -vsync is deprecated in ffmpeg but ffmpex doesn't have the modern replacement, -fps_mode
# |> add_file_option(option_update(1)) # ffmpeg complains but it doesn't necessarily need this. I'm omitting because ffmpex doesn't know this function
# |> add_file_option(option_fps_mode("passthrough")) # -fps_mode is the modern replacement for -vsync
execute(command)
end
end