Updated output path builder to better use yt-dlp variables

This commit is contained in:
Kieran Eglin 2024-02-20 10:02:23 -08:00
parent 7d62f7c7df
commit e5f8c9de4f
No known key found for this signature in database
GPG key ID: 193984967FCF432D
3 changed files with 33 additions and 46 deletions

View file

@ -12,50 +12,22 @@ defmodule Pinchflat.Profiles.Options.YtDlp.OutputPathBuilder do
Translates liquid-style templates into yt-dlp-style templates, Translates liquid-style templates into yt-dlp-style templates,
leaving yt-dlp syntax intact. leaving yt-dlp syntax intact.
IDEA: apart from any custom options I've defined, I can support any yt-dlp
option by assuming `{{ identifier }}` should transform to `%(identifier)S`.
It's not doing anything huge, but it's nicer to type and more approachable IMO.
IDEA: set a default for `MediaProfile`'s `output_path_template` field
""" """
def build(template_string) do def build(template_string) do
TemplateParser.parse(template_string, full_yt_dlp_options_map()) TemplateParser.parse(template_string, custom_yt_dlp_option_map(), &identifier_fn/2)
end end
defp full_yt_dlp_options_map do # The `nil` case simply wraps the identifier in yt-dlp-style syntax. This assumes that
Map.merge( # the identifier is a valid yt-dlp option. The upside is that this gives the user
standard_yt_dlp_option_map(), # access to ALL single-word yt-dlp options in the (imo) more friendly/forgiving liquid-style syntax.
custom_yt_dlp_option_map() #
) # For all "custom" variables, we use the `Map.get/3` function to look up the value in the provided.
end # See `custom_yt_dlp_option_map` for a list of those.
defp identifier_fn(identifier, variables) do
defp standard_yt_dlp_option_map do case Map.get(variables, identifier) do
%{ nil -> "%(#{identifier})S"
# Uppercase "S" means "safe" - ie: filepath-safe value -> value
"id" => "%(id)S", end
"ext" => "%(ext)S",
"title" => "%(title)S",
"fulltitle" => "%(fulltitle)S",
"uploader" => "%(uploader)S",
"creator" => "%(creator)S",
"upload_date" => "%(upload_date)S",
"release_date" => "%(release_date)S",
"duration" => "%(duration)S",
# For videos classified as an episode of a series:
"series" => "%(series)S",
"season" => "%(season)S",
"season_number" => "%(season_number)S",
"episode" => "%(episode)S",
"episode_number" => "%(episode_number)S",
"episode_id" => "%(episode_id)S",
# For videos classified as music:
"track" => "%(track)S",
"track_number" => "%(track_number)S",
"artist" => "%(artist)S",
"album" => "%(album)S",
"album_type" => "%(album_type)S",
"genre" => "%(genre)S"
}
end end
defp custom_yt_dlp_option_map do defp custom_yt_dlp_option_map do

View file

@ -8,28 +8,35 @@ defmodule Pinchflat.RenderedString.Parser do
use Pinchflat.RenderedString.Base use Pinchflat.RenderedString.Base
@doc """ @doc """
Parses a string into a rendered string, using the provided variables. Parses a string into a rendered string, using the provided variables. Optionally
takes a custom fetcher function for handling missing variables.
Variable identifiers are surrounded by {{ and }}. The variable keys MUST be strings. Variable identifiers are surrounded by {{ and }}. The variable keys MUST be strings.
If an identifier is not found in the provided variables, it will be removed from the string. If an identifier is not found in the provided variables, it will be removed from the string.
Returns `{:ok, binary()}` or `{:error, binary()}`.
""" """
def parse(string, variables) do def parse(string, variables, value_fetch_fn \\ &default_fetcher/2) do
# `do_parse` comes from `RenderedString.Base` # `do_parse` comes from `RenderedString.Base`
case do_parse(string) do case do_parse(string) do
{:ok, parsed, _, _, _, _} -> {:ok, parsed, _, _, _, _} ->
{:ok, build_string(parsed, variables)} {:ok, build_string(parsed, variables, value_fetch_fn)}
{:error, message, _, _, _, _} -> {:error, message, _, _, _, _} ->
{:error, message} {:error, message}
end end
end end
defp build_string(parsed, variables) do defp build_string(parsed, variables, value_fetch_fn) do
Enum.reduce(parsed, "", fn element, acc -> Enum.reduce(parsed, "", fn element, acc ->
case element do case element do
{:text, text} -> acc <> text {:text, text} -> acc <> text
{:interpolation, {:identifier, identifier}} -> acc <> to_string(variables[identifier]) {:interpolation, {:identifier, identifier}} -> acc <> value_fetch_fn.(identifier, variables)
end end
end) end)
end end
def default_fetcher(identifier, variables) do
Map.get(variables, identifier, "")
end
end end

View file

@ -3,7 +3,7 @@ defmodule Pinchflat.RenderedString.ParserTest do
alias Pinchflat.RenderedString.Parser alias Pinchflat.RenderedString.Parser
describe "parse/2" do describe "parse/3" do
test "it returns the rendered string when the string is valid" do test "it returns the rendered string when the string is valid" do
assert {:ok, "bar"} = Parser.parse("{{ foo }}", %{"foo" => "bar"}) assert {:ok, "bar"} = Parser.parse("{{ foo }}", %{"foo" => "bar"})
end end
@ -36,5 +36,13 @@ defmodule Pinchflat.RenderedString.ParserTest do
test "it returns an error when the string is invalid" do test "it returns an error when the string is invalid" do
assert {:error, "expected end of string"} = Parser.parse("{{ 1-1 }", %{}) assert {:error, "expected end of string"} = Parser.parse("{{ 1-1 }", %{})
end end
test "it supports a custom fetcher function" do
custom_fetcher = fn _, _ ->
"quux"
end
assert {:ok, "quux"} = Parser.parse("{{ foo }}", %{}, custom_fetcher)
end
end end
end end