Skip to content

Commit

Permalink
[Enhancement] Capture playlist_index for use with output templates (k…
Browse files Browse the repository at this point in the history
…ieraneglin#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
  • Loading branch information
kieraneglin authored Jul 15, 2024
1 parent 5a10015 commit e06e050
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 11 deletions.
1 change: 1 addition & 0 deletions lib/pinchflat/downloading/download_option_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilder do
"source_collection_id" => source.collection_id,
"source_collection_name" => source.collection_name,
"source_collection_type" => to_string(source.collection_type),
"media_playlist_index" => to_string(media_item_with_preloads.playlist_index),
"media_upload_date_index" =>
media_item_with_preloads.upload_date_index
|> to_string()
Expand Down
7 changes: 6 additions & 1 deletion lib/pinchflat/media/media.ex
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,17 @@ defmodule Pinchflat.Media 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))
# Some fields should only be set on insert and not on update.
fields_to_drop_on_update = [:playlist_index]

%MediaItem{}
|> MediaItem.changeset(attrs)
|> Repo.insert(
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]
)
Expand Down
3 changes: 3 additions & 0 deletions lib/pinchflat/media/media_item.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ defmodule Pinchflat.Media.MediaItem do
alias Pinchflat.Media.MediaItemsSearchIndex

@allowed_fields [
# these fields are only captured on index
:playlist_index,
# these fields are captured on indexing (and again on download)
:title,
:media_id,
Expand Down Expand Up @@ -72,6 +74,7 @@ defmodule Pinchflat.Media.MediaItem do
field :uploaded_at, :utc_datetime
field :upload_date_index, :integer, default: 0
field :duration_seconds, :integer
field :playlist_index, :integer, default: 0

field :media_filepath, :string
field :media_size_bytes, :integer
Expand Down
12 changes: 8 additions & 4 deletions lib/pinchflat/yt_dlp/media.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ defmodule Pinchflat.YtDlp.Media do
:livestream,
:short_form_content,
:uploaded_at,
:duration_seconds
:duration_seconds,
:playlist_index
]

alias __MODULE__
Expand Down Expand Up @@ -63,7 +64,7 @@ defmodule Pinchflat.YtDlp.Media do
@doc """
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
runner = Application.get_env(:pinchflat, :yt_dlp_runner)
Expand All @@ -84,9 +85,11 @@ defmodule Pinchflat.YtDlp.Media do

@doc """
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
"%(.{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

@doc """
Expand All @@ -104,7 +107,8 @@ defmodule Pinchflat.YtDlp.Media do
livestream: !!response["was_live"],
duration_seconds: response["duration"] && round(response["duration"]),
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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ defmodule PinchflatWeb.MediaProfiles.MediaProfileHTML do
season_from_date: "alias for upload_year",
season_episode_from_date: "the upload date formatted as sYYYYeMMDD",
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

Expand Down
Binary file modified priv/repo/erd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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
19 changes: 18 additions & 1 deletion test/pinchflat/media_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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 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

Expand Down
23 changes: 19 additions & 4 deletions test/pinchflat/yt_dlp/media_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ defmodule Pinchflat.YtDlp.MediaTest do

describe "indexing_output_template/0" do
test "contains all the greatest hits" do
assert "%(.{id,title,was_live,webpage_url,description,aspect_ratio,duration,upload_date,timestamp})j" ==
Media.indexing_output_template()
attrs = ~w(id title was_live webpage_url description aspect_ratio duration upload_date timestamp playlist_index)a
formatted_attrs = "%(.{#{Enum.join(attrs, ",")}})j"

assert formatted_attrs == Media.indexing_output_template()
end
end

Expand All @@ -126,7 +128,8 @@ defmodule Pinchflat.YtDlp.MediaTest do
"aspect_ratio" => 1.0,
"duration" => 60,
"upload_date" => "20210101",
"timestamp" => 1_600_000_000
"timestamp" => 1_600_000_000,
"playlist_index" => 1
}

assert %Media{
Expand All @@ -137,7 +140,8 @@ defmodule Pinchflat.YtDlp.MediaTest do
livestream: false,
short_form_content: false,
uploaded_at: ~U[2020-09-13 12:26:40Z],
duration_seconds: 60
duration_seconds: 60,
playlist_index: 1
} == Media.response_to_struct(response)
end

Expand Down Expand Up @@ -217,6 +221,17 @@ defmodule Pinchflat.YtDlp.MediaTest do

assert %Media{livestream: false} = Media.response_to_struct(response)
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

describe "response_to_struct/1 when testing uploaded_at" do
Expand Down

0 comments on commit e06e050

Please sign in to comment.