[Feature] Add SponsorBlock-based section removal (#144)
* Added sponsorblock columns to media profile * Added sponsorblock options to profile form * Added SB to download options builder
This commit is contained in:
parent
216e05567f
commit
964ab2f89b
7 changed files with 147 additions and 1 deletions
|
|
@ -22,6 +22,7 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilder do
|
||||||
thumbnail_options(media_item_with_preloads) ++
|
thumbnail_options(media_item_with_preloads) ++
|
||||||
metadata_options(media_profile) ++
|
metadata_options(media_profile) ++
|
||||||
quality_options(media_profile) ++
|
quality_options(media_profile) ++
|
||||||
|
sponsorblock_options(media_profile) ++
|
||||||
output_options(media_item_with_preloads)
|
output_options(media_item_with_preloads)
|
||||||
|
|
||||||
{:ok, built_options}
|
{:ok, built_options}
|
||||||
|
|
@ -116,6 +117,17 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilder do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp sponsorblock_options(media_profile) do
|
||||||
|
categories = media_profile.sponsorblock_categories
|
||||||
|
behaviour = media_profile.sponsorblock_behaviour
|
||||||
|
|
||||||
|
case {behaviour, categories} do
|
||||||
|
{_, []} -> []
|
||||||
|
{:remove, _} -> [sponsorblock_remove: Enum.join(categories, ",")]
|
||||||
|
{:disabled, _} -> []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp output_options(media_item_with_preloads) do
|
defp output_options(media_item_with_preloads) do
|
||||||
[
|
[
|
||||||
output: build_output_path_for(media_item_with_preloads.source)
|
output: build_output_path_for(media_item_with_preloads.source)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ defmodule Pinchflat.Profiles.MediaProfile do
|
||||||
download_metadata
|
download_metadata
|
||||||
embed_metadata
|
embed_metadata
|
||||||
download_nfo
|
download_nfo
|
||||||
|
sponsorblock_behaviour
|
||||||
|
sponsorblock_categories
|
||||||
shorts_behaviour
|
shorts_behaviour
|
||||||
livestream_behaviour
|
livestream_behaviour
|
||||||
preferred_resolution
|
preferred_resolution
|
||||||
|
|
@ -47,6 +49,8 @@ defmodule Pinchflat.Profiles.MediaProfile do
|
||||||
field :embed_metadata, :boolean, default: false
|
field :embed_metadata, :boolean, default: false
|
||||||
|
|
||||||
field :download_nfo, :boolean, default: false
|
field :download_nfo, :boolean, default: false
|
||||||
|
field :sponsorblock_behaviour, Ecto.Enum, values: [:disabled, :remove], default: :disabled
|
||||||
|
field :sponsorblock_categories, {:array, :string}, default: []
|
||||||
# NOTE: these do NOT speed up indexing - the indexer still has to go
|
# NOTE: these do NOT speed up indexing - the indexer still has to go
|
||||||
# through the entire collection to determine if a media is a short or
|
# through the entire collection to determine if a media is a short or
|
||||||
# a livestream.
|
# a livestream.
|
||||||
|
|
|
||||||
|
|
@ -251,7 +251,7 @@ defmodule PinchflatWeb.CoreComponents do
|
||||||
attr :type, :string,
|
attr :type, :string,
|
||||||
default: "text",
|
default: "text",
|
||||||
values: ~w(checkbox color date datetime-local email file hidden month number password
|
values: ~w(checkbox color date datetime-local email file hidden month number password
|
||||||
toggle range radio search select tel text textarea time url week)
|
checkbox_group toggle range radio search select tel text textarea time url week)
|
||||||
|
|
||||||
attr :field, Phoenix.HTML.FormField, doc: "a form field struct retrieved from the form, for example: @form[:email]"
|
attr :field, Phoenix.HTML.FormField, doc: "a form field struct retrieved from the form, for example: @form[:email]"
|
||||||
|
|
||||||
|
|
@ -304,6 +304,33 @@ defmodule PinchflatWeb.CoreComponents do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def input(%{type: "checkbox_group"} = assigns) do
|
||||||
|
~H"""
|
||||||
|
<div phx-feedback-for={@name}>
|
||||||
|
<.label for={@id}>
|
||||||
|
<%= @label %><span :if={@label_suffix} class="text-xs text-bodydark"><%= @label_suffix %></span>
|
||||||
|
</.label>
|
||||||
|
<section class="grid grid-cols-1 gap-2 md:grid-cols-2 max-w-prose mb-4 ml-1">
|
||||||
|
<div :for={{option_name, option_value} <- @options} class="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id={"#{@id}-#{option_value}"}
|
||||||
|
name={"#{@name}[]"}
|
||||||
|
value={option_value}
|
||||||
|
checked={option_value in @value}
|
||||||
|
class={["rounded focus:ring-offset-0 ring-offset-0 focus:ring-0 h-5 w-5 ", @inputclass]}
|
||||||
|
/>
|
||||||
|
<label for={"#{@id}-#{option_value}"} class="ml-2 cursor-pointer select-none">
|
||||||
|
<%= option_name %>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<.help :if={@help}><%= @help %></.help>
|
||||||
|
<.error :for={msg <- @errors}><%= msg %></.error>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
def input(%{type: "toggle"} = assigns) do
|
def input(%{type: "toggle"} = assigns) do
|
||||||
assigns =
|
assigns =
|
||||||
assign_new(assigns, :checked, fn ->
|
assign_new(assigns, :checked, fn ->
|
||||||
|
|
@ -601,6 +628,10 @@ defmodule PinchflatWeb.CoreComponents do
|
||||||
_ ->
|
_ ->
|
||||||
true
|
true
|
||||||
end)
|
end)
|
||||||
|
|> Enum.map(fn
|
||||||
|
{k, v} when is_list(v) -> {k, Enum.join(v, ", ")}
|
||||||
|
rest -> rest
|
||||||
|
end)
|
||||||
|
|
||||||
assigns = assign(assigns, iterable_attributes: attrs)
|
assigns = assign(assigns, iterable_attributes: attrs)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,26 @@ defmodule PinchflatWeb.MediaProfiles.MediaProfileHTML do
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def friendly_sponsorblock_options do
|
||||||
|
[
|
||||||
|
{"Disabled (default)", "disabled"},
|
||||||
|
{"Remove Segments", "remove"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def frieldly_sponsorblock_categories do
|
||||||
|
[
|
||||||
|
{"Sponsor", "sponsor"},
|
||||||
|
{"Intro/Intermission", "intro"},
|
||||||
|
{"Outro/Credits", "outro"},
|
||||||
|
{"Self Promotion", "selfpromo"},
|
||||||
|
{"Preview/Recap", "preview"},
|
||||||
|
{"Filler Tangent", "filler"},
|
||||||
|
{"Interaction Reminder", "interaction"},
|
||||||
|
{"Non-music Section", "music_offtopic"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
def custom_output_template_options do
|
def custom_output_template_options do
|
||||||
%{
|
%{
|
||||||
upload_day: nil,
|
upload_day: nil,
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,36 @@
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<h3 class="mt-10 text-2xl text-black dark:text-white">
|
||||||
|
SponsorBlock Options
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<section x-data="{ sponsorblockBehaviour: null }">
|
||||||
|
<section x-data="{ presets: { default: 'disabled', media_center: 'disabled', audio: 'disabled', archiving: 'disabled' } }">
|
||||||
|
<.input
|
||||||
|
field={f[:sponsorblock_behaviour]}
|
||||||
|
options={friendly_sponsorblock_options()}
|
||||||
|
type="select"
|
||||||
|
label="SponsorBlock Behaviour"
|
||||||
|
help="Action to take when SponsorBlock segments are found. 'Disabled' won't take any action"
|
||||||
|
x-model="sponsorblockBehaviour"
|
||||||
|
x-init="
|
||||||
|
sponsorblockBehaviour = $el.value
|
||||||
|
$watch('selectedPreset', p => p && ($el.value = presets[p]))
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section x-show="sponsorblockBehaviour !== 'disabled'" x-transition>
|
||||||
|
<.input
|
||||||
|
field={f[:sponsorblock_categories]}
|
||||||
|
options={frieldly_sponsorblock_categories()}
|
||||||
|
type="checkbox_group"
|
||||||
|
label="SponsorBlock Categories"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
|
||||||
<.button class="my-10 sm:mb-7.5 w-full sm:w-auto" rounding="rounded-lg">Save Media profile</.button>
|
<.button class="my-10 sm:mb-7.5 w-full sm:w-auto" rounding="rounded-lg">Save Media profile</.button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
defmodule Pinchflat.Repo.Migrations.AddSponsorblockToMediaProfiles do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:media_profiles) do
|
||||||
|
add :sponsorblock_behaviour, :string, default: "disabled"
|
||||||
|
add :sponsorblock_categories, {:array, :string}, default: []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -222,6 +222,45 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilderTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "build/1 when testing sponsorblock options" do
|
||||||
|
test "includes :sponsorblock_remove option when specified", %{media_item: media_item} do
|
||||||
|
media_item =
|
||||||
|
update_media_profile_attribute(media_item, %{
|
||||||
|
sponsorblock_behaviour: :remove,
|
||||||
|
sponsorblock_categories: ["sponsor", "intro"]
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||||
|
|
||||||
|
assert {:sponsorblock_remove, "sponsor,intro"} in res
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not include :sponsorblock_remove option without categories", %{media_item: media_item} do
|
||||||
|
media_item =
|
||||||
|
update_media_profile_attribute(media_item, %{
|
||||||
|
sponsorblock_behaviour: :remove,
|
||||||
|
sponsorblock_categories: []
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||||
|
|
||||||
|
refute {:sponsorblock_remove, ""} in res
|
||||||
|
refute {:sponsorblock_remove, []} in res
|
||||||
|
refute :sponsorblock_remove in res
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not include any sponsorblock options when disabled", %{media_item: media_item} do
|
||||||
|
media_item =
|
||||||
|
update_media_profile_attribute(media_item, %{sponsorblock_behaviour: :disabled})
|
||||||
|
|
||||||
|
assert {:ok, res} = DownloadOptionBuilder.build(media_item)
|
||||||
|
|
||||||
|
refute {:sponsorblock_remove, ""} in res
|
||||||
|
refute {:sponsorblock_remove, []} in res
|
||||||
|
refute :sponsorblock_remove in res
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "build_output_path_for/1" do
|
describe "build_output_path_for/1" do
|
||||||
test "builds an output path for a source", %{media_item: media_item} do
|
test "builds an output path for a source", %{media_item: media_item} do
|
||||||
path = DownloadOptionBuilder.build_output_path_for(media_item.source)
|
path = DownloadOptionBuilder.build_output_path_for(media_item.source)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue