Basic PNG generation in Elixir
parent
135720df52
commit
e0511053a4
@ -1,4 +1,7 @@
|
||||
defmodule Asciinema.FileStore do
|
||||
@doc "Serves file at given path in store"
|
||||
@callback serve_file(conn :: %Plug.Conn{}, path :: String.t, filename :: String.t) :: %Plug.Conn{}
|
||||
|
||||
@doc "Opens the given path in store"
|
||||
@callback open(path :: String.t) :: {:ok, File.io_device} | {:error, File.posix}
|
||||
end
|
||||
|
@ -0,0 +1,10 @@
|
||||
defmodule Asciinema.PngGenerator do
|
||||
alias Asciinema.Asciicast
|
||||
|
||||
@doc "Generates PNG image from asciicast and returns path to it"
|
||||
@callback generate(asciicast :: %Asciicast{}) :: {:ok, String.t} | {:error, term}
|
||||
|
||||
def generate(asciicast) do
|
||||
Application.get_env(:asciinema, :png_generator).generate(asciicast)
|
||||
end
|
||||
end
|
@ -0,0 +1,55 @@
|
||||
defmodule Asciinema.PngGenerator.A2png do
|
||||
@behaviour Asciinema.PngGenerator
|
||||
use GenServer
|
||||
alias Asciinema.Asciicast
|
||||
|
||||
@result_timeout 30000
|
||||
@acquire_timeout 5000
|
||||
|
||||
def generate(%Asciicast{} = asciicast) do
|
||||
{:ok, tmp_dir_path} = Briefly.create(directory: true)
|
||||
|
||||
:poolboy.transaction(
|
||||
:worker,
|
||||
&GenServer.call(&1, {:gen_png, asciicast, tmp_dir_path}, @result_timeout), @acquire_timeout
|
||||
)
|
||||
end
|
||||
|
||||
# GenServer API
|
||||
|
||||
def start_link(_) do
|
||||
GenServer.start_link(__MODULE__, nil, [])
|
||||
end
|
||||
|
||||
def init(_) do
|
||||
{:ok, nil}
|
||||
end
|
||||
|
||||
def handle_call({:gen_png, asciicast, tmp_dir_path}, _from, state) do
|
||||
{:reply, do_gen(asciicast, tmp_dir_path), state}
|
||||
end
|
||||
|
||||
defp do_gen(asciicast, tmp_dir_path) do
|
||||
path = Asciicast.json_store_path(asciicast)
|
||||
json_path = Path.join(tmp_dir_path, "tmp.json")
|
||||
png_path = Path.join(tmp_dir_path, "tmp.png")
|
||||
snapshot_at = "#{asciicast.duration / 2}"
|
||||
|
||||
with {:ok, file} <- file_store().open(path),
|
||||
{:ok, _} <- :file.copy(file, json_path),
|
||||
%{status: 0} <- Porcelain.exec(bin_path(), [json_path, png_path, snapshot_at]) do
|
||||
{:ok, png_path}
|
||||
else
|
||||
otherwise ->
|
||||
otherwise
|
||||
end
|
||||
end
|
||||
|
||||
def bin_path do
|
||||
Keyword.get(Application.get_env(:asciinema, __MODULE__), :bin_path)
|
||||
end
|
||||
|
||||
defp file_store do
|
||||
Application.get_env(:asciinema, :file_store)
|
||||
end
|
||||
end
|
@ -0,0 +1,15 @@
|
||||
defmodule Asciinema.AsciicastImageController do
|
||||
use Asciinema.Web, :controller
|
||||
alias Asciinema.{Repo, Asciicast, PngGenerator}
|
||||
alias Plug.MIME
|
||||
|
||||
def show(conn, %{"id" => id} = _params) do
|
||||
asciicast = Repo.one!(Asciicast.by_id_or_secret_token(id))
|
||||
{:ok, png_path} = PngGenerator.generate(asciicast)
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-type", MIME.path(png_path))
|
||||
|> send_file(200, png_path)
|
||||
|> halt
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue