Initial yt-dlp implementation (#3)

* Added basic runner for yt-dlp commands

* Updated formatting rules

* Added JSON runner; Added test script for CLI interactions

* Reworked yt-dlp runner; added proper test mocks
This commit is contained in:
Kieran
2024-01-19 22:26:32 -08:00
committed by GitHub
parent c03fd837ea
commit 89b1640eb6
16 changed files with 259 additions and 10 deletions
@@ -0,0 +1,7 @@
defmodule Pinchflat.DownloaderBackends.BackendCommandRunner do
@moduledoc """
A behaviour for running CLI commands against a downloader backend
"""
@callback run(binary(), keyword()) :: {:ok, binary()} | {:error, binary(), integer()}
end
@@ -0,0 +1,58 @@
defmodule Pinchflat.DownloaderBackends.YtDlp.CommandRunner do
@moduledoc """
Runs yt-dlp commands using the `System.cmd/3` function
"""
alias Pinchflat.Utils.StringUtils
alias Pinchflat.DownloaderBackends.BackendCommandRunner
@behaviour BackendCommandRunner
@doc """
Runs a yt-dlp command and returns the string output
"""
@impl BackendCommandRunner
def run(url, command_opts) do
command = backend_executable()
formatted_command_opts = parse_options(command_opts) ++ [url]
case System.cmd(command, formatted_command_opts, stderr_to_stdout: true) do
{output, 0} -> {:ok, output}
{output, status} -> {:error, output, status}
end
end
# We want to satisfy the following behaviours:
#
# 1. If the key is an atom, convert it to a string and convert it to kebab case (for convenience)
# 2. If the key is a string, assume we want it as-is and don't convert it
# 3. If the key is accompanied by a value, append the value to the list
# 4. If the key is not accompanied by a value, assume it's a flag and PREpend it to the list
defp parse_options(command_opts) do
Enum.reduce(command_opts, [], &parse_option/2)
end
defp parse_option({k, v}, acc) when is_atom(k) do
stringified_key = StringUtils.to_kebab_case(Atom.to_string(k))
parse_option({"--#{stringified_key}", v}, acc)
end
defp parse_option({k, v}, acc) when is_binary(k) do
acc ++ [k, to_string(v)]
end
defp parse_option(arg, acc) when is_atom(arg) do
stringified_arg = StringUtils.to_kebab_case(Atom.to_string(arg))
parse_option("--#{stringified_arg}", acc)
end
defp parse_option(arg, acc) when is_binary(arg) do
[arg | acc]
end
defp backend_executable do
Application.get_env(:pinchflat, :yt_dlp_executable)
end
end
@@ -0,0 +1,21 @@
defmodule Pinchflat.DownloaderBackends.YtDlp.VideoCollection do
@moduledoc """
Contains utilities for working with collections of videos (ie: channels, playlists)
"""
@doc """
Returns a list of strings representing the video ids in the collection
"""
def get_video_ids(url, command_opts \\ []) do
opts = command_opts ++ [:simulate, :skip_download, :get_id]
case backend_runner().run(url, opts) do
{:ok, output} -> {:ok, String.split(output, "\n", trim: true)}
res -> res
end
end
defp backend_runner do
Application.get_env(:pinchflat, :yt_dlp_runner)
end
end
@@ -614,8 +614,7 @@ defmodule PinchflatWeb.CoreComponents do
to: selector,
time: 200,
transition:
{"transition-all transform ease-in duration-200",
"opacity-100 translate-y-0 sm:scale-100",
{"transition-all transform ease-in duration-200", "opacity-100 translate-y-0 sm:scale-100",
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
)
end
+1 -2
View File
@@ -70,8 +70,7 @@ defmodule PinchflatWeb.Telemetry do
),
summary("pinchflat.repo.query.idle_time",
unit: {:native, :millisecond},
description:
"The time the connection spent waiting before being checked out for the query"
description: "The time the connection spent waiting before being checked out for the query"
),
# VM Metrics
+14
View File
@@ -0,0 +1,14 @@
defmodule Pinchflat.Utils.StringUtils do
@moduledoc """
Utility functions for working with strings
"""
@doc """
Converts a string to kebab-case (ie: `hello world` -> `hello-world`)
"""
def to_kebab_case(string) do
string
|> String.replace(~r/[\s_]/, "-")
|> String.downcase()
end
end