* [WIP] hooked up photo downloading to source metadata worker * Added new attributes for source images * Added option to profile form
127 lines
4.5 KiB
Elixir
127 lines
4.5 KiB
Elixir
defmodule Pinchflat.YtDlp.MediaCollection do
|
|
@moduledoc """
|
|
Contains utilities for working with collections of
|
|
media (aka: a source [ie: channels, playlists]).
|
|
"""
|
|
|
|
require Logger
|
|
|
|
alias Pinchflat.Filesystem.FilesystemHelpers
|
|
alias Pinchflat.YtDlp.Media, as: YtDlpMedia
|
|
|
|
@doc """
|
|
Returns a list of maps representing the media in the collection.
|
|
|
|
Options:
|
|
- :file_listener_handler - a function that will be called with the path to the
|
|
file that will be written to when yt-dlp is done. This is useful for
|
|
setting up a file watcher to know when the file is ready to be read.
|
|
|
|
Returns {:ok, [map()]} | {:error, any, ...}.
|
|
"""
|
|
def get_media_attributes_for_collection(url, addl_opts \\ []) do
|
|
runner = Application.get_env(:pinchflat, :yt_dlp_runner)
|
|
# `ignore_no_formats_error` is necessary because yt-dlp will error out if
|
|
# the first video has not released yet (ie: is a premier). We don't care about
|
|
# available formats since we're just getting the media details
|
|
command_opts = [:simulate, :skip_download, :ignore_no_formats_error]
|
|
output_template = YtDlpMedia.indexing_output_template()
|
|
output_filepath = FilesystemHelpers.generate_metadata_tmpfile(:json)
|
|
file_listener_handler = Keyword.get(addl_opts, :file_listener_handler, false)
|
|
|
|
if file_listener_handler do
|
|
file_listener_handler.(output_filepath)
|
|
end
|
|
|
|
case runner.run(url, command_opts, output_template, output_filepath: output_filepath) do
|
|
{:ok, output} ->
|
|
parsed_lines =
|
|
output
|
|
|> String.split("\n", trim: true)
|
|
|> Enum.map(fn line ->
|
|
case Phoenix.json_library().decode(line) do
|
|
{:ok, parsed_json} ->
|
|
YtDlpMedia.response_to_struct(parsed_json)
|
|
|
|
_ ->
|
|
nil
|
|
end
|
|
end)
|
|
|
|
{:ok, Enum.filter(parsed_lines, &(&1 != nil))}
|
|
|
|
res ->
|
|
res
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets a source's ID and name from its URL.
|
|
|
|
yt-dlp does not _really_ have source-specific functions that return what
|
|
we need, so instead we're fetching just the first video (using playlist_end: 1)
|
|
and parsing the source ID and name from _its_ metadata
|
|
|
|
Returns {:ok, map()} | {:error, any, ...}.
|
|
"""
|
|
def get_source_details(source_url, addl_opts \\ []) do
|
|
# `ignore_no_formats_error` is necessary because yt-dlp will error out if
|
|
# the first video has not released yet (ie: is a premier). We don't care about
|
|
# available formats since we're just getting the source details
|
|
command_opts = [:simulate, :skip_download, :ignore_no_formats_error, playlist_end: 1] ++ addl_opts
|
|
output_template = "%(.{channel,channel_id,playlist_id,playlist_title,filename})j"
|
|
|
|
with {:ok, output} <- backend_runner().run(source_url, command_opts, output_template),
|
|
{:ok, parsed_json} <- Phoenix.json_library().decode(output) do
|
|
{:ok, format_source_details(parsed_json)}
|
|
else
|
|
err -> err
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets a source's metadata from its URL.
|
|
|
|
This is mostly for things like getting the source's avatar and banner image
|
|
(if applicable). However, this yt-dlp call doesn't have enough overlap with
|
|
`get_source_details/1` to allow combining them - this one has _almost_ everything
|
|
we need, but it doesn't contain enough information to tell 100% if the url is a channel
|
|
or a playlist.
|
|
|
|
The main purpose of this (past using as a fetcher for _other_ metadata) is to live
|
|
as a compressed blob for possible future use. That's why it's not getting formatted like
|
|
`get_source_details/1`
|
|
|
|
Returns {:ok, map()} | {:error, any, ...}.
|
|
"""
|
|
def get_source_metadata(source_url, addl_opts \\ []) do
|
|
opts = [playlist_items: 0] ++ addl_opts
|
|
output_template = "playlist:%()j"
|
|
|
|
with {:ok, output} <- backend_runner().run(source_url, opts, output_template),
|
|
{:ok, parsed_json} <- Phoenix.json_library().decode(output) do
|
|
{:ok, parsed_json}
|
|
else
|
|
err -> err
|
|
end
|
|
end
|
|
|
|
defp format_source_details(response) do
|
|
# NOTE: I should probably make this a struct some day
|
|
%{
|
|
channel_id: response["channel_id"],
|
|
channel_name: response["channel"],
|
|
playlist_id: response["playlist_id"],
|
|
playlist_name: response["playlist_title"],
|
|
# It's not a name, it's a path dammit!
|
|
# This actually isn't used for the inital response - it's
|
|
# used later to update a source's metadata
|
|
filepath: response["filename"]
|
|
}
|
|
end
|
|
|
|
defp backend_runner do
|
|
# This approach lets us mock the command for testing
|
|
Application.get_env(:pinchflat, :yt_dlp_runner)
|
|
end
|
|
end
|