[Enhancement] Capture playlist_index for use with output templates (#315)
* Started capturing playlist_index on indexing pass * Added playlist_index as a media item field * Added playlist index to output variable templates * Improved the way playlist_indexes are rejected on update * Updated docs * Undid unneeded changes
This commit is contained in:
parent
5a10015634
commit
e06e050f95
9 changed files with 67 additions and 11 deletions
|
|
@ -203,6 +203,7 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilder do
|
||||||
"source_collection_id" => source.collection_id,
|
"source_collection_id" => source.collection_id,
|
||||||
"source_collection_name" => source.collection_name,
|
"source_collection_name" => source.collection_name,
|
||||||
"source_collection_type" => to_string(source.collection_type),
|
"source_collection_type" => to_string(source.collection_type),
|
||||||
|
"media_playlist_index" => to_string(media_item_with_preloads.playlist_index),
|
||||||
"media_upload_date_index" =>
|
"media_upload_date_index" =>
|
||||||
media_item_with_preloads.upload_date_index
|
media_item_with_preloads.upload_date_index
|
||||||
|> to_string()
|
|> to_string()
|
||||||
|
|
|
||||||
|
|
@ -142,12 +142,17 @@ defmodule Pinchflat.Media do
|
||||||
"""
|
"""
|
||||||
def create_media_item_from_backend_attrs(source, media_attrs_struct) do
|
def create_media_item_from_backend_attrs(source, media_attrs_struct) do
|
||||||
attrs = Map.merge(%{source_id: source.id}, Map.from_struct(media_attrs_struct))
|
attrs = Map.merge(%{source_id: source.id}, Map.from_struct(media_attrs_struct))
|
||||||
|
# Some fields should only be set on insert and not on update.
|
||||||
|
fields_to_drop_on_update = [:playlist_index]
|
||||||
|
|
||||||
%MediaItem{}
|
%MediaItem{}
|
||||||
|> MediaItem.changeset(attrs)
|
|> MediaItem.changeset(attrs)
|
||||||
|> Repo.insert(
|
|> Repo.insert(
|
||||||
on_conflict: [
|
on_conflict: [
|
||||||
set: Map.to_list(attrs)
|
set:
|
||||||
|
attrs
|
||||||
|
|> Map.drop(fields_to_drop_on_update)
|
||||||
|
|> Map.to_list()
|
||||||
],
|
],
|
||||||
conflict_target: [:source_id, :media_id]
|
conflict_target: [:source_id, :media_id]
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ defmodule Pinchflat.Media.MediaItem do
|
||||||
alias Pinchflat.Media.MediaItemsSearchIndex
|
alias Pinchflat.Media.MediaItemsSearchIndex
|
||||||
|
|
||||||
@allowed_fields [
|
@allowed_fields [
|
||||||
|
# these fields are only captured on index
|
||||||
|
:playlist_index,
|
||||||
# these fields are captured on indexing (and again on download)
|
# these fields are captured on indexing (and again on download)
|
||||||
:title,
|
:title,
|
||||||
:media_id,
|
:media_id,
|
||||||
|
|
@ -72,6 +74,7 @@ defmodule Pinchflat.Media.MediaItem do
|
||||||
field :uploaded_at, :utc_datetime
|
field :uploaded_at, :utc_datetime
|
||||||
field :upload_date_index, :integer, default: 0
|
field :upload_date_index, :integer, default: 0
|
||||||
field :duration_seconds, :integer
|
field :duration_seconds, :integer
|
||||||
|
field :playlist_index, :integer, default: 0
|
||||||
|
|
||||||
field :media_filepath, :string
|
field :media_filepath, :string
|
||||||
field :media_size_bytes, :integer
|
field :media_size_bytes, :integer
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ defmodule Pinchflat.YtDlp.Media do
|
||||||
:livestream,
|
:livestream,
|
||||||
:short_form_content,
|
:short_form_content,
|
||||||
:uploaded_at,
|
:uploaded_at,
|
||||||
:duration_seconds
|
:duration_seconds,
|
||||||
|
:playlist_index
|
||||||
]
|
]
|
||||||
|
|
||||||
alias __MODULE__
|
alias __MODULE__
|
||||||
|
|
@ -63,7 +64,7 @@ defmodule Pinchflat.YtDlp.Media do
|
||||||
@doc """
|
@doc """
|
||||||
Returns a map representing the media at the given URL.
|
Returns a map representing the media at the given URL.
|
||||||
|
|
||||||
Returns {:ok, [map()]} | {:error, any, ...}.
|
Returns {:ok, %Media{}} | {:error, any, ...}.
|
||||||
"""
|
"""
|
||||||
def get_media_attributes(url) do
|
def get_media_attributes(url) do
|
||||||
runner = Application.get_env(:pinchflat, :yt_dlp_runner)
|
runner = Application.get_env(:pinchflat, :yt_dlp_runner)
|
||||||
|
|
@ -84,9 +85,11 @@ defmodule Pinchflat.YtDlp.Media do
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns the output template for yt-dlp's indexing command.
|
Returns the output template for yt-dlp's indexing command.
|
||||||
|
|
||||||
|
NOTE: playlist_index is really only useful for playlists that will never change their order.
|
||||||
"""
|
"""
|
||||||
def indexing_output_template do
|
def indexing_output_template do
|
||||||
"%(.{id,title,was_live,webpage_url,description,aspect_ratio,duration,upload_date,timestamp})j"
|
"%(.{id,title,was_live,webpage_url,description,aspect_ratio,duration,upload_date,timestamp,playlist_index})j"
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
@ -104,7 +107,8 @@ defmodule Pinchflat.YtDlp.Media do
|
||||||
livestream: !!response["was_live"],
|
livestream: !!response["was_live"],
|
||||||
duration_seconds: response["duration"] && round(response["duration"]),
|
duration_seconds: response["duration"] && round(response["duration"]),
|
||||||
short_form_content: response["webpage_url"] && short_form_content?(response),
|
short_form_content: response["webpage_url"] && short_form_content?(response),
|
||||||
uploaded_at: response["upload_date"] && parse_uploaded_at(response)
|
uploaded_at: response["upload_date"] && parse_uploaded_at(response),
|
||||||
|
playlist_index: response["playlist_index"] || 0
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,9 @@ defmodule PinchflatWeb.MediaProfiles.MediaProfileHTML do
|
||||||
season_from_date: "alias for upload_year",
|
season_from_date: "alias for upload_year",
|
||||||
season_episode_from_date: "the upload date formatted as sYYYYeMMDD",
|
season_episode_from_date: "the upload date formatted as sYYYYeMMDD",
|
||||||
season_episode_index_from_date:
|
season_episode_index_from_date:
|
||||||
"the upload date formatted as sYYYYeMMDDII where II is an index to prevent date collisions"
|
"the upload date formatted as sYYYYeMMDDII where II is an index to prevent date collisions",
|
||||||
|
media_playlist_index:
|
||||||
|
"the place of the media item in the playlist. Do not use with channels. May not work if the playlist is updated"
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 493 KiB After Width: | Height: | Size: 424 KiB |
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Pinchflat.Repo.Migrations.AddPlaylistIndexToMediaItems do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:media_items) do
|
||||||
|
add :playlist_index, :integer, null: false, default: 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -689,7 +689,24 @@ defmodule Pinchflat.MediaTest do
|
||||||
assert {:ok, %MediaItem{} = media_item_2} = Media.create_media_item_from_backend_attrs(source, different_attrs)
|
assert {:ok, %MediaItem{} = media_item_2} = Media.create_media_item_from_backend_attrs(source, different_attrs)
|
||||||
|
|
||||||
assert media_item_1.id == media_item_2.id
|
assert media_item_1.id == media_item_2.id
|
||||||
assert media_item_2.title == different_attrs.title
|
assert Repo.reload(media_item_2).title == different_attrs.title
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't update fields like playlist_index" do
|
||||||
|
source = source_fixture()
|
||||||
|
|
||||||
|
media_attrs =
|
||||||
|
media_attributes_return_fixture()
|
||||||
|
|> Phoenix.json_library().decode!()
|
||||||
|
|> Map.put("playlist_index", 1)
|
||||||
|
|> YtDlpMedia.response_to_struct()
|
||||||
|
|
||||||
|
different_attrs = %YtDlpMedia{media_attrs | playlist_index: 9999}
|
||||||
|
|
||||||
|
assert {:ok, %MediaItem{} = _media_item_1} = Media.create_media_item_from_backend_attrs(source, media_attrs)
|
||||||
|
assert {:ok, %MediaItem{} = media_item_2} = Media.create_media_item_from_backend_attrs(source, different_attrs)
|
||||||
|
|
||||||
|
assert Repo.reload(media_item_2).playlist_index == media_attrs.playlist_index
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,8 +110,10 @@ defmodule Pinchflat.YtDlp.MediaTest do
|
||||||
|
|
||||||
describe "indexing_output_template/0" do
|
describe "indexing_output_template/0" do
|
||||||
test "contains all the greatest hits" do
|
test "contains all the greatest hits" do
|
||||||
assert "%(.{id,title,was_live,webpage_url,description,aspect_ratio,duration,upload_date,timestamp})j" ==
|
attrs = ~w(id title was_live webpage_url description aspect_ratio duration upload_date timestamp playlist_index)a
|
||||||
Media.indexing_output_template()
|
formatted_attrs = "%(.{#{Enum.join(attrs, ",")}})j"
|
||||||
|
|
||||||
|
assert formatted_attrs == Media.indexing_output_template()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -126,7 +128,8 @@ defmodule Pinchflat.YtDlp.MediaTest do
|
||||||
"aspect_ratio" => 1.0,
|
"aspect_ratio" => 1.0,
|
||||||
"duration" => 60,
|
"duration" => 60,
|
||||||
"upload_date" => "20210101",
|
"upload_date" => "20210101",
|
||||||
"timestamp" => 1_600_000_000
|
"timestamp" => 1_600_000_000,
|
||||||
|
"playlist_index" => 1
|
||||||
}
|
}
|
||||||
|
|
||||||
assert %Media{
|
assert %Media{
|
||||||
|
|
@ -137,7 +140,8 @@ defmodule Pinchflat.YtDlp.MediaTest do
|
||||||
livestream: false,
|
livestream: false,
|
||||||
short_form_content: false,
|
short_form_content: false,
|
||||||
uploaded_at: ~U[2020-09-13 12:26:40Z],
|
uploaded_at: ~U[2020-09-13 12:26:40Z],
|
||||||
duration_seconds: 60
|
duration_seconds: 60,
|
||||||
|
playlist_index: 1
|
||||||
} == Media.response_to_struct(response)
|
} == Media.response_to_struct(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -217,6 +221,17 @@ defmodule Pinchflat.YtDlp.MediaTest do
|
||||||
|
|
||||||
assert %Media{livestream: false} = Media.response_to_struct(response)
|
assert %Media{livestream: false} = Media.response_to_struct(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "doesn't blow up if playlist_index is missing" do
|
||||||
|
response = %{
|
||||||
|
"webpage_url" => "https://www.youtube.com/watch?v=TiZPUDkDYbk",
|
||||||
|
"aspect_ratio" => 1.0,
|
||||||
|
"duration" => nil,
|
||||||
|
"upload_date" => "20210101"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert %Media{playlist_index: 0} = Media.response_to_struct(response)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "response_to_struct/1 when testing uploaded_at" do
|
describe "response_to_struct/1 when testing uploaded_at" do
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue