From: href Date: Fri, 28 Aug 2020 19:14:28 +0000 (+0200) Subject: Rewrite MP4/MOV binaries to be faststart X-Git-Url: https://git.squeep.com/?a=commitdiff_plain;h=dfceb03cf47374fdeab60784476b2e266208a4bb;p=akkoma Rewrite MP4/MOV binaries to be faststart In some cases, MP4/MOV files can have the data _before_ the meta-data. Thus, ffmpeg (and all similar tools) cannot really process the input if it's given over stdin/streaming/pipes. BUT I REALLY DON'T WANT TO MAKE TEMPORARY FILES so here we go, an implementation of qtfaststart in elixir. --- diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index b42612ccb..5ac75b326 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -14,8 +14,7 @@ defmodule Pleroma.Helpers.MediaHelper do {:ok, args} <- prepare_image_resize_args(options), url = Pleroma.Web.MediaProxy.url(url), {:ok, env} <- Pleroma.HTTP.get(url), - {:ok, fifo_path} <- mkfifo() - do + {:ok, fifo_path} <- mkfifo() do args = List.flatten([fifo_path, args]) run_fifo(fifo_path, env, executable, args) else @@ -27,12 +26,17 @@ defmodule Pleroma.Helpers.MediaHelper do defp prepare_image_resize_args(%{max_width: max_width, max_height: max_height} = options) do quality = options[:quality] || 85 resize = Enum.join([max_width, "x", max_height, ">"]) + args = [ - "-interlace", "Plane", - "-resize", resize, - "-quality", to_string(quality), - "jpg:-" + "-interlace", + "Plane", + "-resize", + resize, + "-quality", + to_string(quality), + "jpg:-" ] + {:ok, args} end @@ -45,11 +49,15 @@ defmodule Pleroma.Helpers.MediaHelper do {:ok, fifo_path} <- mkfifo(), args = [ "-y", - "-i", fifo_path, - "-vframes", "1", - "-f", "mjpeg", - "-loglevel", "error", - "pipe:" + "-i", + fifo_path, + "-vframes", + "1", + "-f", + "mjpeg", + "-loglevel", + "error", + "-" ] do run_fifo(fifo_path, env, executable, args) else @@ -59,9 +67,18 @@ defmodule Pleroma.Helpers.MediaHelper do end defp run_fifo(fifo_path, env, executable, args) do - pid = Port.open({:spawn_executable, executable}, [:use_stdio, :stream, :exit_status, :binary, args: args]) + pid = + Port.open({:spawn_executable, executable}, [ + :use_stdio, + :stream, + :exit_status, + :binary, + args: args + ]) + fifo = Port.open(to_charlist(fifo_path), [:eof, :binary, :stream, :out]) - true = Port.command(fifo, env.body) + fix = Pleroma.Helpers.QtFastStart.fix(env.body) + true = Port.command(fifo, fix) :erlang.port_close(fifo) loop_recv(pid) after @@ -70,10 +87,12 @@ defmodule Pleroma.Helpers.MediaHelper do defp mkfifo() do path = "#{@tmp_base}#{to_charlist(:erlang.phash2(self()))}" + case System.cmd("mkfifo", [path]) do {_, 0} -> spawn(fifo_guard(path)) {:ok, path} + {_, err} -> {:error, {:fifo_failed, err}} end @@ -81,8 +100,10 @@ defmodule Pleroma.Helpers.MediaHelper do defp fifo_guard(path) do pid = self() - fn() -> + + fn -> ref = Process.monitor(pid) + receive do {:DOWN, ^ref, :process, ^pid, _} -> File.rm(path) @@ -98,14 +119,16 @@ defmodule Pleroma.Helpers.MediaHelper do receive do {^pid, {:data, data}} -> loop_recv(pid, acc <> data) + {^pid, {:exit_status, 0}} -> {:ok, acc} + {^pid, {:exit_status, status}} -> {:error, status} - after - 5000 -> - :erlang.port_close(pid) - {:error, :timeout} + after + 5000 -> + :erlang.port_close(pid) + {:error, :timeout} end end end diff --git a/lib/pleroma/helpers/qt_fast_start.ex b/lib/pleroma/helpers/qt_fast_start.ex new file mode 100644 index 000000000..694b583b9 --- /dev/null +++ b/lib/pleroma/helpers/qt_fast_start.ex @@ -0,0 +1,131 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Helpers.QtFastStart do + @moduledoc """ + (WIP) Converts a "slow start" (data before metadatas) mov/mp4 file to a "fast start" one (metadatas before data). + """ + + # TODO: Cleanup and optimizations + # Inspirations: https://www.ffmpeg.org/doxygen/3.4/qt-faststart_8c_source.html + # https://github.com/danielgtaylor/qtfaststart/blob/master/qtfaststart/processor.py + # ISO/IEC 14496-12:2015, ISO/IEC 15444-12:2015 + # Paracetamol + + def fix(binary = <<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70, _::binary>>) do + index = fix(binary, binary, 0, []) + + case index do + [{"ftyp", _, _, _, _}, {"mdat", _, _, _, _} | _] -> faststart(index) + [{"ftyp", _, _, _, _}, {"free", _, _, _, _}, {"mdat", _, _, _, _} | _] -> faststart(index) + _ -> binary + end + end + + def fix(binary) do + binary + end + + defp fix(<<>>, _bin, _pos, acc) do + :lists.reverse(acc) + end + + defp fix( + <>, + bin, + pos, + acc + ) do + if fourcc == "mdat" && size == 0 do + # mdat with 0 size means "seek to the end" -- also, in that case the file is probably OK. + acc = [ + {fourcc, pos, byte_size(bin) - pos, byte_size(bin) - pos, + <>} + | acc + ] + + fix(<<>>, bin, byte_size(bin), acc) + else + full_size = size - 8 + <> = rest + + acc = [ + {fourcc, pos, pos + size, size, + <>} + | acc + ] + + fix(rest, bin, pos + size, acc) + end + end + + defp faststart(index) do + {{_ftyp, _, _, _, ftyp}, index} = List.keytake(index, "ftyp", 0) + + # Skip re-writing the free fourcc as it's kind of useless. Why stream useless bytes when you can do without? + {free_size, index} = + case List.keytake(index, "free", 0) do + {{_, _, _, size, _}, index} -> {size, index} + _ -> {0, index} + end + + {{_moov, _, _, moov_size, moov}, index} = List.keytake(index, "moov", 0) + offset = -free_size + moov_size + rest = for {_, _, _, _, data} <- index, do: data, into: <<>> + <> = moov + new_moov = fix_moov(moov_data, offset) + <> + end + + defp fix_moov(moov, offset) do + fix_moov(moov, offset, <<>>) + end + + defp fix_moov(<<>>, _, acc), do: acc + + defp fix_moov( + <>, + offset, + acc + ) do + full_size = size - 8 + <> = rest + + data = + cond do + fourcc in ["trak", "mdia", "minf", "stbl"] -> + # Theses contains sto or co64 part + <>)::binary>> + + fourcc in ["stco", "co64"] -> + # fix the damn thing + <> = data + + entry_size = + case fourcc do + "stco" -> 4 + "co64" -> 8 + end + + {_, result} = + Enum.reduce(1..count, {rest, <<>>}, fn _, + {<>, acc} -> + {rest, <>} + end) + + <> + + true -> + <> + end + + acc = <> + fix_moov(rest, offset, acc) + end +end