diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js index 08f407f..49d9b55 100644 --- a/assets/tailwind.config.js +++ b/assets/tailwind.config.js @@ -11,7 +11,8 @@ module.exports = { darkMode: 'class', theme: { fontFamily: { - satoshi: ['Satoshi', 'sans-serif'] + satoshi: ['Satoshi', 'sans-serif'], + ...defaultTheme.fontFamily }, screens: { '2xsm': '375px', diff --git a/dev.Dockerfile b/dev.Dockerfile index 84b1faf..19ec99e 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -25,7 +25,7 @@ WORKDIR /app COPY . ./ # Needs permissions to be updated AFTER the copy step -RUN chmod +x ./docker-run.sh +RUN chmod +x ./docker-run.dev.sh # Install Elixir deps RUN mix deps.get diff --git a/lib/pinchflat/profiles/media_profile.ex b/lib/pinchflat/profiles/media_profile.ex index cc1a7fa..848eae6 100644 --- a/lib/pinchflat/profiles/media_profile.ex +++ b/lib/pinchflat/profiles/media_profile.ex @@ -29,7 +29,7 @@ defmodule Pinchflat.Profiles.MediaProfile do schema "media_profiles" do field :name, :string - field :output_path_template, :string, default: "/{{ uploader }}/{{ title }}/{{ title }}-{{ id }}.{{ ext }}" + field :output_path_template, :string, default: "/{{ channel }}/{{ title }}/{{ title }} [{{ id }}].{{ ext }}" field :download_subs, :boolean, default: true field :download_auto_subs, :boolean, default: true diff --git a/lib/pinchflat/profiles/options/yt_dlp/output_path_builder.ex b/lib/pinchflat/profiles/options/yt_dlp/output_path_builder.ex index 8356e88..dadb9fe 100644 --- a/lib/pinchflat/profiles/options/yt_dlp/output_path_builder.ex +++ b/lib/pinchflat/profiles/options/yt_dlp/output_path_builder.ex @@ -12,50 +12,22 @@ defmodule Pinchflat.Profiles.Options.YtDlp.OutputPathBuilder do Translates liquid-style templates into yt-dlp-style templates, 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 - TemplateParser.parse(template_string, full_yt_dlp_options_map()) + TemplateParser.parse(template_string, custom_yt_dlp_option_map(), &identifier_fn/2) end - defp full_yt_dlp_options_map do - Map.merge( - standard_yt_dlp_option_map(), - custom_yt_dlp_option_map() - ) - end - - defp standard_yt_dlp_option_map do - %{ - # Uppercase "S" means "safe" - ie: filepath-safe - "id" => "%(id)S", - "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" - } + # The `nil` case simply wraps the identifier in yt-dlp-style syntax. This assumes that + # the identifier is a valid yt-dlp option. The upside is that this gives the user + # access to ALL single-word yt-dlp options in the (imo) more friendly/forgiving liquid-style syntax. + # + # For all "custom" variables, we use the `Map.get/3` function to look up the value in the provided. + # See `custom_yt_dlp_option_map` for a list of those. + defp identifier_fn(identifier, variables) do + case Map.get(variables, identifier) do + nil -> "%(#{identifier})S" + value -> value + end end defp custom_yt_dlp_option_map do diff --git a/lib/pinchflat/rendered_string/parser.ex b/lib/pinchflat/rendered_string/parser.ex index ab167fa..103588d 100644 --- a/lib/pinchflat/rendered_string/parser.ex +++ b/lib/pinchflat/rendered_string/parser.ex @@ -8,28 +8,35 @@ defmodule Pinchflat.RenderedString.Parser do use Pinchflat.RenderedString.Base @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. 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` case do_parse(string) do {:ok, parsed, _, _, _, _} -> - {:ok, build_string(parsed, variables)} + {:ok, build_string(parsed, variables, value_fetch_fn)} {:error, message, _, _, _, _} -> {:error, message} end end - defp build_string(parsed, variables) do + defp build_string(parsed, variables, value_fetch_fn) do Enum.reduce(parsed, "", fn element, acc -> case element do {:text, text} -> acc <> text - {:interpolation, {:identifier, identifier}} -> acc <> to_string(variables[identifier]) + {:interpolation, {:identifier, identifier}} -> acc <> value_fetch_fn.(identifier, variables) end end) end + + def default_fetcher(identifier, variables) do + Map.get(variables, identifier, "") + end end diff --git a/lib/pinchflat_web.ex b/lib/pinchflat_web.ex index 0df8659..e0a512d 100644 --- a/lib/pinchflat_web.ex +++ b/lib/pinchflat_web.ex @@ -88,6 +88,7 @@ defmodule PinchflatWeb do # Core UI components and translation import PinchflatWeb.Gettext import PinchflatWeb.CoreComponents + import PinchflatWeb.CustomComponents.TextComponents import PinchflatWeb.CustomComponents.TableComponents import PinchflatWeb.CustomComponents.ButtonComponents diff --git a/lib/pinchflat_web/components/core_components.ex b/lib/pinchflat_web/components/core_components.ex index bcf8e02..939169b 100644 --- a/lib/pinchflat_web/components/core_components.ex +++ b/lib/pinchflat_web/components/core_components.ex @@ -256,6 +256,7 @@ defmodule PinchflatWeb.CoreComponents do attr :prompt, :string, default: nil, doc: "the prompt for select inputs" attr :options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2" attr :multiple, :boolean, default: false, doc: "the multiple flag for select inputs" + attr :inputclass, :string, default: "" attr :rest, :global, include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength multiple pattern placeholder readonly required rows size step) @@ -281,7 +282,15 @@ defmodule PinchflatWeb.CoreComponents do
+ <%= render_slot(@inner_block) %>
+
+ """
+ end
+
+ @doc """
+ Renders a reference link with the given href and content.
+ """
+ attr :href, :string, required: true
+ slot :inner_block
+
+ def reference_link(assigns) do
+ ~H"""
+ <.link href={@href} target="_blank" class="text-blue-500 hover:text-blue-300">
+ <%= render_slot(@inner_block) %>
+
+ """
+ end
+end
diff --git a/lib/pinchflat_web/controllers/media_profiles/media_profile_html.ex b/lib/pinchflat_web/controllers/media_profiles/media_profile_html.ex
index 31133c1..41840f0 100644
--- a/lib/pinchflat_web/controllers/media_profiles/media_profile_html.ex
+++ b/lib/pinchflat_web/controllers/media_profiles/media_profile_html.ex
@@ -28,4 +28,21 @@ defmodule PinchflatWeb.MediaProfiles.MediaProfileHTML do
{"360p", "360p"}
]
end
+
+ def custom_output_template_options do
+ ~w(upload_day upload_month upload_year)a
+ end
+
+ def common_output_template_options do
+ ~w(
+ id
+ ext
+ title
+ fulltitle
+ uploader
+ channel
+ upload_date
+ duration_string
+ )a
+ end
end
diff --git a/lib/pinchflat_web/controllers/media_profiles/media_profile_html/media_profile_form.html.heex b/lib/pinchflat_web/controllers/media_profiles/media_profile_html/media_profile_form.html.heex
index 21c006c..0786aa7 100644
--- a/lib/pinchflat_web/controllers/media_profiles/media_profile_html/media_profile_form.html.heex
+++ b/lib/pinchflat_web/controllers/media_profiles/media_profile_html/media_profile_form.html.heex
@@ -6,11 +6,13 @@
General Options
<.input field={f[:name]} type="text" label="Name" placeholder="New Profile" help="(required)" />
+
<.input
field={f[:output_path_template]}
type="text"
+ inputclass="font-mono"
label="Output path template"
- help="TODO: provide docs (required)"
+ help="Must end with .{{ ext }}. See below for more details. I promise the default is good for most cases (required)"
/>