[Enhancement] Allow overriding output templates on a per-source basis (#179)
* Added output path override to table and download option builder * Added output template override to UI
This commit is contained in:
parent
3acdb5ccba
commit
2c717e8e7e
9 changed files with 227 additions and 97 deletions
|
|
@ -3,6 +3,7 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilder do
|
||||||
Builds the options for yt-dlp to download media based on the given media profile.
|
Builds the options for yt-dlp to download media based on the given media profile.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
alias Pinchflat.Sources
|
||||||
alias Pinchflat.Sources.Source
|
alias Pinchflat.Sources.Source
|
||||||
alias Pinchflat.Media.MediaItem
|
alias Pinchflat.Media.MediaItem
|
||||||
alias Pinchflat.Downloading.OutputPathBuilder
|
alias Pinchflat.Downloading.OutputPathBuilder
|
||||||
|
|
@ -30,12 +31,12 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilder do
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Builds the output path for yt-dlp to download media based on the given source's
|
Builds the output path for yt-dlp to download media based on the given source's
|
||||||
media profile.
|
media profile. Uses the source's override output path template if it exists.
|
||||||
|
|
||||||
Returns binary()
|
Returns binary()
|
||||||
"""
|
"""
|
||||||
def build_output_path_for(%Source{} = source_with_preloads) do
|
def build_output_path_for(%Source{} = source_with_preloads) do
|
||||||
output_path_template = source_with_preloads.media_profile.output_path_template
|
output_path_template = Sources.output_path_template(source_with_preloads)
|
||||||
|
|
||||||
build_output_path(output_path_template, source_with_preloads)
|
build_output_path(output_path_template, source_with_preloads)
|
||||||
end
|
end
|
||||||
|
|
@ -184,7 +185,7 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilder do
|
||||||
# It's dependent on the output_path_template being a string ending `.{{ ext }}`
|
# It's dependent on the output_path_template being a string ending `.{{ ext }}`
|
||||||
# (or equivalent), but that's validated by the MediaProfile schema.
|
# (or equivalent), but that's validated by the MediaProfile schema.
|
||||||
defp determine_thumbnail_location(media_item_with_preloads) do
|
defp determine_thumbnail_location(media_item_with_preloads) do
|
||||||
output_path_template = media_item_with_preloads.source.media_profile.output_path_template
|
output_path_template = Sources.output_path_template(media_item_with_preloads.source)
|
||||||
|
|
||||||
output_path_template
|
output_path_template
|
||||||
|> String.split(~r{\.}, include_captures: true)
|
|> String.split(~r{\.}, include_captures: true)
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,8 @@ defmodule Pinchflat.Profiles.MediaProfile do
|
||||||
|> unique_constraint(:name)
|
|> unique_constraint(:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp ext_regex do
|
@doc false
|
||||||
|
def ext_regex do
|
||||||
~r/\.({{ ?ext ?}}|%\( ?ext ?\)[sS])$/
|
~r/\.({{ ?ext ?}}|%\( ?ext ?\)[sS])$/
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ defmodule Pinchflat.Sources.Source do
|
||||||
retention_period_days
|
retention_period_days
|
||||||
title_filter_regex
|
title_filter_regex
|
||||||
media_profile_id
|
media_profile_id
|
||||||
|
output_path_template_override
|
||||||
)a
|
)a
|
||||||
|
|
||||||
# Expensive API calls are made when a source is inserted/updated so
|
# Expensive API calls are made when a source is inserted/updated so
|
||||||
|
|
@ -76,6 +77,7 @@ defmodule Pinchflat.Sources.Source do
|
||||||
field :retention_period_days, :integer
|
field :retention_period_days, :integer
|
||||||
field :original_url, :string
|
field :original_url, :string
|
||||||
field :title_filter_regex, :string
|
field :title_filter_regex, :string
|
||||||
|
field :output_path_template_override, :string
|
||||||
|
|
||||||
field :series_directory, :string
|
field :series_directory, :string
|
||||||
field :nfo_filepath, :string
|
field :nfo_filepath, :string
|
||||||
|
|
@ -109,6 +111,8 @@ defmodule Pinchflat.Sources.Source do
|
||||||
|> dynamic_default(:uuid, fn _ -> Ecto.UUID.generate() end)
|
|> dynamic_default(:uuid, fn _ -> Ecto.UUID.generate() end)
|
||||||
|> validate_required(required_fields)
|
|> validate_required(required_fields)
|
||||||
|> validate_number(:retention_period_days, greater_than_or_equal_to: 0)
|
|> validate_number(:retention_period_days, greater_than_or_equal_to: 0)
|
||||||
|
# Ensures it ends with `.{{ ext }}` or `.%(ext)s` or similar (with a little wiggle room)
|
||||||
|
|> validate_format(:output_path_template_override, MediaProfile.ext_regex(), message: "must end with .{{ ext }}")
|
||||||
|> cast_assoc(:metadata, with: &SourceMetadata.changeset/2, required: false)
|
|> cast_assoc(:metadata, with: &SourceMetadata.changeset/2, required: false)
|
||||||
|> unique_constraint([:collection_id, :media_profile_id, :title_filter_regex], error_key: :original_url)
|
|> unique_constraint([:collection_id, :media_profile_id, :title_filter_regex], error_key: :original_url)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,19 @@ defmodule Pinchflat.Sources do
|
||||||
alias Pinchflat.SlowIndexing.SlowIndexingHelpers
|
alias Pinchflat.SlowIndexing.SlowIndexingHelpers
|
||||||
alias Pinchflat.Metadata.SourceMetadataStorageWorker
|
alias Pinchflat.Metadata.SourceMetadataStorageWorker
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the relevant output path template for a source.
|
||||||
|
Pulls from the source's override if present, otherwise uses the media profile's.
|
||||||
|
|
||||||
|
Returns binary()
|
||||||
|
"""
|
||||||
|
def output_path_template(source) do
|
||||||
|
source = Repo.preload(source, :media_profile)
|
||||||
|
media_profile = source.media_profile
|
||||||
|
|
||||||
|
source.output_path_template_override || media_profile.output_path_template
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns the list of sources. Returns [%Source{}, ...]
|
Returns the list of sources. Returns [%Source{}, ...]
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,20 @@ defmodule PinchflatWeb.Sources.SourceHTML do
|
||||||
def rss_feed_url(conn, source) do
|
def rss_feed_url(conn, source) do
|
||||||
url(conn, ~p"/sources/#{source.uuid}/feed") <> ".xml"
|
url(conn, ~p"/sources/#{source.uuid}/feed") <> ".xml"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def output_path_template_override_placeholders(media_profiles) do
|
||||||
|
media_profiles
|
||||||
|
|> Enum.map(&{&1.id, &1.output_path_template})
|
||||||
|
|> Map.new()
|
||||||
|
|> Phoenix.json_library().encode!()
|
||||||
|
end
|
||||||
|
|
||||||
|
def output_path_template_override_help do
|
||||||
|
help_button_classes = "underline decoration-bodydark decoration-1 hover:decoration-white cursor-pointer"
|
||||||
|
help_button = ~s{<span class="#{help_button_classes}" x-on:click="$dispatch('load-template')">Click here</span>}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Must end with .{{ ext }}. Same rules as Media Profile output path templates. #{help_button} to load your media profile's output template
|
||||||
|
"""
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -9,113 +9,139 @@
|
||||||
Oops, something went wrong! Please check the errors below.
|
Oops, something went wrong! Please check the errors below.
|
||||||
</.error>
|
</.error>
|
||||||
|
|
||||||
<section class="flex justify-between items-center mt-8">
|
<section x-data="{ mediaProfileId: null }">
|
||||||
<h3 class=" text-2xl text-black dark:text-white">
|
<section class="flex justify-between items-center mt-8">
|
||||||
General Options
|
<h3 class=" text-2xl text-black dark:text-white">
|
||||||
</h3>
|
General Options
|
||||||
<span class="cursor-pointer hover:underline" x-on:click="advancedMode = !advancedMode">
|
</h3>
|
||||||
Editing Mode: <span x-text="advancedMode ? 'Advanced' : 'Basic'"></span>
|
<span class="cursor-pointer hover:underline" x-on:click="advancedMode = !advancedMode">
|
||||||
</span>
|
Editing Mode: <span x-text="advancedMode ? 'Advanced' : 'Basic'"></span>
|
||||||
</section>
|
</span>
|
||||||
|
</section>
|
||||||
|
|
||||||
<.input
|
|
||||||
field={f[:custom_name]}
|
|
||||||
type="text"
|
|
||||||
label="Custom Name"
|
|
||||||
help="Something descriptive. Does not impact indexing or downloading"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<.input field={f[:original_url]} type="text" label="Source URL" help="URL of a channel or playlist (required)" />
|
|
||||||
|
|
||||||
<.input
|
|
||||||
field={f[:media_profile_id]}
|
|
||||||
options={Enum.map(@media_profiles, &{&1.name, &1.id})}
|
|
||||||
type="select"
|
|
||||||
label="Media Profile"
|
|
||||||
help="Sets your preferences for what media to look for and how to store it"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<h3 class="mt-8 text-2xl text-black dark:text-white">
|
|
||||||
Indexing Options
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<section x-data="{ fastIndexingEnabled: null }">
|
|
||||||
<.input
|
<.input
|
||||||
field={f[:index_frequency_minutes]}
|
field={f[:custom_name]}
|
||||||
options={friendly_index_frequencies()}
|
type="text"
|
||||||
type="select"
|
label="Custom Name"
|
||||||
label="Index Frequency"
|
help="Something descriptive. Does not impact indexing or downloading"
|
||||||
x-bind:disabled="fastIndexingEnabled == true"
|
|
||||||
x-init="$watch('fastIndexingEnabled', v => v && ($el.value = 30 * 24 * 60))"
|
|
||||||
help="Indexing is the process of checking for media to download. Sets the time between one index of this source finishing and the next one starting"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div phx-click={show_modal("upgrade-modal")}>
|
<.input field={f[:original_url]} type="text" label="Source URL" help="URL of a channel or playlist (required)" />
|
||||||
|
|
||||||
|
<.input
|
||||||
|
field={f[:media_profile_id]}
|
||||||
|
options={Enum.map(@media_profiles, &{&1.name, &1.id})}
|
||||||
|
type="select"
|
||||||
|
label="Media Profile"
|
||||||
|
help="Sets your preferences for what media to look for and how to store it"
|
||||||
|
x-model.fill="mediaProfileId"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h3 class="mt-8 text-2xl text-black dark:text-white">
|
||||||
|
Indexing Options
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<section x-data="{ fastIndexingEnabled: null }">
|
||||||
<.input
|
<.input
|
||||||
field={f[:fast_index]}
|
field={f[:index_frequency_minutes]}
|
||||||
type="toggle"
|
options={friendly_index_frequencies()}
|
||||||
label="Use Fast Indexing"
|
type="select"
|
||||||
label_suffix="(pro)"
|
label="Index Frequency"
|
||||||
help="Experimental. Overrides 'Index Frequency'. Recommended for large channels that upload frequently. Does not work with private playlists. See below for more info"
|
x-bind:disabled="fastIndexingEnabled == true"
|
||||||
x-init="
|
x-init="$watch('fastIndexingEnabled', v => v && ($el.value = 30 * 24 * 60))"
|
||||||
|
help="Indexing is the process of checking for media to download. Sets the time between one index of this source finishing and the next one starting"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div phx-click={show_modal("upgrade-modal")}>
|
||||||
|
<.input
|
||||||
|
field={f[:fast_index]}
|
||||||
|
type="toggle"
|
||||||
|
label="Use Fast Indexing"
|
||||||
|
label_suffix="(pro)"
|
||||||
|
help="Experimental. Overrides 'Index Frequency'. Recommended for large channels that upload frequently. Does not work with private playlists. See below for more info"
|
||||||
|
x-init="
|
||||||
// `enabled` is the data attribute that the toggle uses internally
|
// `enabled` is the data attribute that the toggle uses internally
|
||||||
fastIndexingEnabled = enabled
|
fastIndexingEnabled = enabled
|
||||||
$watch('enabled', value => fastIndexingEnabled = !!value)
|
$watch('enabled', value => fastIndexingEnabled = !!value)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<h3 class="mt-8 text-2xl text-black dark:text-white">
|
|
||||||
Downloading Options
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<.input
|
|
||||||
field={f[:download_media]}
|
|
||||||
type="toggle"
|
|
||||||
label="Download Media"
|
|
||||||
help="Unchecking still indexes media but it won't be downloaded until you enable this option"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<.input
|
|
||||||
field={f[:download_cutoff_date]}
|
|
||||||
type="text"
|
|
||||||
label="Download Cutoff Date"
|
|
||||||
placeholder="YYYY-MM-DD"
|
|
||||||
maxlength="10"
|
|
||||||
pattern="((?:19|20)[0-9][0-9])-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])"
|
|
||||||
title="YYYY-MM-DD"
|
|
||||||
help="Only download media uploaded after this date. Leave blank to download all media. Must be in YYYY-MM-DD format"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<.input
|
|
||||||
field={f[:retention_period_days]}
|
|
||||||
type="number"
|
|
||||||
label="Retention Period (days)"
|
|
||||||
min="0"
|
|
||||||
help="Days between when media is *downloaded* and when it's deleted. Leave blank to keep media indefinitely"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<section x-show="advancedMode">
|
|
||||||
<h3 class="mt-8 text-2xl text-black dark:text-white">
|
<h3 class="mt-8 text-2xl text-black dark:text-white">
|
||||||
Advanced Options
|
Downloading Options
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm mt-2">
|
|
||||||
Tread carefully
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<.input
|
<.input
|
||||||
field={f[:title_filter_regex]}
|
field={f[:download_media]}
|
||||||
type="text"
|
type="toggle"
|
||||||
label="Title Filter Regex"
|
label="Download Media"
|
||||||
placeholder="(?i)^How to Bike$"
|
help="Unchecking still indexes media but it won't be downloaded until you enable this option"
|
||||||
help="A PCRE-compatible regex. Only media with titles that match this regex will be downloaded. Look up 'SQLean Regex docs' for more"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<.input
|
||||||
|
field={f[:download_cutoff_date]}
|
||||||
|
type="text"
|
||||||
|
label="Download Cutoff Date"
|
||||||
|
placeholder="YYYY-MM-DD"
|
||||||
|
maxlength="10"
|
||||||
|
pattern="((?:19|20)[0-9][0-9])-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])"
|
||||||
|
title="YYYY-MM-DD"
|
||||||
|
help="Only download media uploaded after this date. Leave blank to download all media. Must be in YYYY-MM-DD format"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<.input
|
||||||
|
field={f[:retention_period_days]}
|
||||||
|
type="number"
|
||||||
|
label="Retention Period (days)"
|
||||||
|
min="0"
|
||||||
|
help="Days between when media is *downloaded* and when it's deleted. Leave blank to keep media indefinitely"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<section x-show="advancedMode">
|
||||||
|
<h3 class="mt-8 text-2xl text-black dark:text-white">
|
||||||
|
Advanced Options
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm mt-2">
|
||||||
|
Tread carefully
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<.input
|
||||||
|
field={f[:title_filter_regex]}
|
||||||
|
type="text"
|
||||||
|
label="Title Filter Regex"
|
||||||
|
placeholder="(?i)^How to Bike$"
|
||||||
|
help="A PCRE-compatible regex. Only media with titles that match this regex will be downloaded. Look up 'SQLean Regex docs' for more"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<section
|
||||||
|
x-data={
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
placeholders: JSON.parse('#{output_path_template_override_placeholders(@media_profiles)}'),
|
||||||
|
inputValue: null
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
x-on:load-template="inputValue = placeholders[mediaProfileId]"
|
||||||
|
>
|
||||||
|
<.input
|
||||||
|
field={f[:output_path_template_override]}
|
||||||
|
type="text"
|
||||||
|
inputclass="font-mono"
|
||||||
|
label="Output path template override"
|
||||||
|
help={output_path_template_override_help()}
|
||||||
|
html_help={true}
|
||||||
|
x-bind:placeholder="placeholders[mediaProfileId]"
|
||||||
|
x-model.fill="inputValue"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<.button class="my-10 sm:mb-7.5 w-full sm:w-auto" rounding="rounded-lg">Save Source</.button>
|
||||||
|
|
||||||
|
<div class="rounded-sm dark:bg-meta-4 p-4 md:p-6 mb-5">
|
||||||
|
<.fast_indexing_help />
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<.button class="my-10 sm:mb-7.5 w-full sm:w-auto" rounding="rounded-lg">Save Source</.button>
|
|
||||||
|
|
||||||
<div class="rounded-sm dark:bg-meta-4 p-4 md:p-6 mb-5">
|
|
||||||
<.fast_indexing_help />
|
|
||||||
</div>
|
|
||||||
</.simple_form>
|
</.simple_form>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Pinchflat.Repo.Migrations.AddOutputTemplateToSources do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:sources) do
|
||||||
|
add :output_path_template_override, :string
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -4,6 +4,7 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilderTest do
|
||||||
import Pinchflat.SourcesFixtures
|
import Pinchflat.SourcesFixtures
|
||||||
import Pinchflat.ProfilesFixtures
|
import Pinchflat.ProfilesFixtures
|
||||||
|
|
||||||
|
alias Pinchflat.Sources
|
||||||
alias Pinchflat.Profiles
|
alias Pinchflat.Profiles
|
||||||
alias Pinchflat.Utils.FilesystemUtils
|
alias Pinchflat.Utils.FilesystemUtils
|
||||||
alias Pinchflat.Downloading.DownloadOptionBuilder
|
alias Pinchflat.Downloading.DownloadOptionBuilder
|
||||||
|
|
@ -31,6 +32,20 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilderTest do
|
||||||
|
|
||||||
assert {:output, "/tmp/test/media/#{media_item.source.custom_name}.%(ext)s"} in res
|
assert {:output, "/tmp/test/media/#{media_item.source.custom_name}.%(ext)s"} in res
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "uses source's output override if present", %{media_item: media_item} do
|
||||||
|
source = media_item.source
|
||||||
|
{:ok, _} = Sources.update_source(source, %{output_path_template_override: "override.%(ext)s"})
|
||||||
|
|
||||||
|
media_item =
|
||||||
|
media_item
|
||||||
|
|> Repo.reload()
|
||||||
|
|> Repo.preload(source: :media_profile)
|
||||||
|
|
||||||
|
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||||
|
|
||||||
|
assert {:output, "/tmp/test/media/override.%(ext)s"} in res
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "build/1 when testing default options" do
|
describe "build/1 when testing default options" do
|
||||||
|
|
@ -135,6 +150,20 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilderTest do
|
||||||
assert {:output, "thumbnail:/tmp/test/media/%(title)S-thumb.%(ext)s"} in res
|
assert {:output, "thumbnail:/tmp/test/media/%(title)S-thumb.%(ext)s"} in res
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "appends -thumb to source's output path override, if present", %{media_item: media_item} do
|
||||||
|
media_item = update_media_profile_attribute(media_item, %{download_thumbnail: true})
|
||||||
|
{:ok, _} = Sources.update_source(media_item.source, %{output_path_template_override: "override.%(ext)s"})
|
||||||
|
|
||||||
|
media_item =
|
||||||
|
media_item
|
||||||
|
|> Repo.reload()
|
||||||
|
|> Repo.preload(source: :media_profile)
|
||||||
|
|
||||||
|
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||||
|
|
||||||
|
assert {:output, "thumbnail:/tmp/test/media/override-thumb.%(ext)s"} in res
|
||||||
|
end
|
||||||
|
|
||||||
test "converts thumbnail to jpg when download_thumbnail is true", %{media_item: media_item} do
|
test "converts thumbnail to jpg when download_thumbnail is true", %{media_item: media_item} do
|
||||||
media_item = update_media_profile_attribute(media_item, %{download_thumbnail: true})
|
media_item = update_media_profile_attribute(media_item, %{download_thumbnail: true})
|
||||||
|
|
||||||
|
|
@ -359,6 +388,15 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilderTest do
|
||||||
|
|
||||||
assert path == "/tmp/test/media/%(title)S.%(ext)s"
|
assert path == "/tmp/test/media/%(title)S.%(ext)s"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "uses source's output override if present", %{media_item: media_item} do
|
||||||
|
source = media_item.source
|
||||||
|
{:ok, source} = Sources.update_source(source, %{output_path_template_override: "override.%(ext)s"})
|
||||||
|
|
||||||
|
path = DownloadOptionBuilder.build_output_path_for(source)
|
||||||
|
|
||||||
|
assert path == "/tmp/test/media/override.%(ext)s"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp update_media_profile_attribute(media_item_with_preloads, attrs) do
|
defp update_media_profile_attribute(media_item_with_preloads, attrs) do
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,28 @@ defmodule Pinchflat.SourcesTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "output_path_template/1" do
|
||||||
|
test "returns the source's override if present" do
|
||||||
|
source = source_fixture(%{output_path_template_override: "/override/{{ title }}.{{ ext }}"})
|
||||||
|
|
||||||
|
assert Sources.output_path_template(source) == "/override/{{ title }}.{{ ext }}"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns the media profile's template if no override is present" do
|
||||||
|
media_profile = media_profile_fixture(%{output_path_template: "/profile/{{ title }}.{{ ext }}"})
|
||||||
|
source = source_fixture(%{media_profile_id: media_profile.id})
|
||||||
|
|
||||||
|
assert Sources.output_path_template(source) == "/profile/{{ title }}.{{ ext }}"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Treats empty strings as being blank" do
|
||||||
|
media_profile = media_profile_fixture(%{output_path_template: "/profile/{{ title }}.{{ ext }}"})
|
||||||
|
source = source_fixture(%{media_profile_id: media_profile.id, output_path_template_override: " "})
|
||||||
|
|
||||||
|
assert Sources.output_path_template(source) == "/profile/{{ title }}.{{ ext }}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "list_sources/0" do
|
describe "list_sources/0" do
|
||||||
test "it returns all sources" do
|
test "it returns all sources" do
|
||||||
source = source_fixture()
|
source = source_fixture()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue