Removed Channel yt-dlp module, instead throwing things in the VideoCollection module

This commit is contained in:
Kieran Eglin 2024-02-02 10:27:00 -08:00
parent 2250695a3e
commit 6a31d333ad
No known key found for this signature in database
GPG key ID: 193984967FCF432D
5 changed files with 80 additions and 107 deletions

View file

@ -1,32 +0,0 @@
defmodule Pinchflat.MediaClient.Backends.YtDlp.Channel do
@moduledoc """
Contains utilities for working with a channel's videos
"""
use Pinchflat.MediaClient.Backends.YtDlp.VideoCollection
alias Pinchflat.MediaClient.SourceDetails
@doc """
Gets a channel's ID and name from its URL.
yt-dlp does not _really_ have channel-specific functions, so
instead we're fetching just the first video (using playlist_end: 1)
and parsing the channel ID and name from _its_ metadata
Returns {:ok, %SourceDetails{}} | {:error, any, ...}.
"""
def get_source_details(channel_url) do
opts = [:skip_download, playlist_end: 1]
with {:ok, output} <- backend_runner().run(channel_url, opts, "%(.{channel,channel_id})j"),
{:ok, parsed_json} <- Phoenix.json_library().decode(output) do
{:ok, SourceDetails.new(parsed_json["channel_id"], parsed_json["channel"])}
else
err -> err
end
end
defp backend_runner do
Application.get_env(:pinchflat, :yt_dlp_runner)
end
end

View file

@ -1,28 +1,47 @@
defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollection do
@moduledoc """
Contains utilities for working with collections of videos (ie: channels, playlists).
Meant to be included in other modules but can be used on its own. Channels and playlists
will have many of their own methods, but also share a lot of methods. This module is for
those shared methods.
Contains utilities for working with collections of
videos (aka: a source [ie: channels, playlists]).
"""
defmacro __using__(_) do
quote do
@doc """
Returns a list of strings representing the video ids in the collection.
alias Pinchflat.MediaClient.SourceDetails
Returns {:ok, [binary()]} | {:error, any, ...}.
"""
def get_video_ids(url, command_opts \\ []) do
runner = Application.get_env(:pinchflat, :yt_dlp_runner)
opts = command_opts ++ [:simulate, :skip_download]
@doc """
Returns a list of strings representing the video ids in the collection.
case runner.run(url, opts, "%(id)s") do
{:ok, output} -> {:ok, String.split(output, "\n", trim: true)}
res -> res
end
end
Returns {:ok, [binary()]} | {:error, any, ...}.
"""
def get_video_ids(url, command_opts \\ []) do
runner = Application.get_env(:pinchflat, :yt_dlp_runner)
opts = command_opts ++ [:simulate, :skip_download]
case runner.run(url, opts, "%(id)s") do
{:ok, output} -> {:ok, String.split(output, "\n", trim: true)}
res -> res
end
end
@doc """
Gets a source's ID and name from its URL.
yt-dlp does not _really_ have source-specific functions, 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, %SourceDetails{}} | {:error, any, ...}.
"""
def get_source_details(source_url) do
opts = [:skip_download, playlist_end: 1]
with {:ok, output} <- backend_runner().run(source_url, opts, "%(.{channel,channel_id})j"),
{:ok, parsed_json} <- Phoenix.json_library().decode(output) do
{:ok, SourceDetails.new(parsed_json["channel_id"], parsed_json["channel"])}
else
err -> err
end
end
defp backend_runner do
Application.get_env(:pinchflat, :yt_dlp_runner)
end
end

View file

@ -8,7 +8,7 @@ defmodule Pinchflat.MediaClient.SourceDetails do
@enforce_keys [:id, :name]
defstruct [:id, :name]
alias Pinchflat.MediaClient.Backends.YtDlp.Channel, as: YtDlpChannel
alias Pinchflat.MediaClient.Backends.YtDlp.VideoCollection, as: YtDlpSource
@doc false
def new(id, name) do
@ -35,7 +35,7 @@ defmodule Pinchflat.MediaClient.SourceDetails do
defp source_module(backend) do
case backend do
:yt_dlp -> YtDlpChannel
:yt_dlp -> YtDlpSource
end
end
end

View file

@ -1,45 +0,0 @@
defmodule Pinchflat.MediaClient.Backends.YtDlp.ChannelTest do
use ExUnit.Case, async: true
import Mox
alias Pinchflat.MediaClient.SourceDetails
alias Pinchflat.MediaClient.Backends.YtDlp.Channel
@channel_url "https://www.youtube.com/c/TheUselessTrials"
setup :verify_on_exit!
describe "get_source_details/1" do
test "it returns a %SourceDetails{} with data on success" do
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot ->
{:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"}
end)
assert {:ok, res} = Channel.get_source_details(@channel_url)
assert %SourceDetails{id: "UCQH2", name: "TheUselessTrials"} = res
end
test "it passes the expected args to the backend runner" do
expect(YtDlpRunnerMock, :run, fn @channel_url, opts, ot ->
assert opts == [:skip_download, playlist_end: 1]
assert ot == "%(.{channel,channel_id})j"
{:ok, "{}"}
end)
assert {:ok, _} = Channel.get_source_details(@channel_url)
end
test "it returns an error if the runner returns an error" do
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:error, "Big issue", 1} end)
assert {:error, "Big issue", 1} = Channel.get_source_details(@channel_url)
end
test "it returns an error if the output is not JSON" do
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "Not JSON"} end)
assert {:error, %Jason.DecodeError{}} = Channel.get_source_details(@channel_url)
end
end
end

View file

@ -2,13 +2,10 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do
use ExUnit.Case, async: true
import Mox
alias Pinchflat.MediaClient.SourceDetails
alias Pinchflat.MediaClient.Backends.YtDlp.VideoCollection
@channel_url "https://www.youtube.com/@TheUselessTrials"
defmodule VideoCollectionUser do
use VideoCollection
end
@channel_url "https://www.youtube.com/c/TheUselessTrials"
setup :verify_on_exit!
@ -16,7 +13,7 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do
test "returns a list of video ids with no blank elements" do
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "id1\nid2\n\nid3\n"} end)
assert {:ok, ["id1", "id2", "id3"]} = VideoCollectionUser.get_video_ids(@channel_url)
assert {:ok, ["id1", "id2", "id3"]} = VideoCollection.get_video_ids(@channel_url)
end
test "it passes the expected default args" do
@ -27,7 +24,7 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do
{:ok, ""}
end)
assert {:ok, _} = VideoCollectionUser.get_video_ids(@channel_url)
assert {:ok, _} = VideoCollection.get_video_ids(@channel_url)
end
test "it passes the expected custom args" do
@ -37,13 +34,47 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do
{:ok, ""}
end)
assert {:ok, _} = VideoCollectionUser.get_video_ids(@channel_url, [:custom_arg])
assert {:ok, _} = VideoCollection.get_video_ids(@channel_url, [:custom_arg])
end
test "returns the error straight through when the command fails" do
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:error, "Big issue", 1} end)
assert {:error, "Big issue", 1} = VideoCollectionUser.get_video_ids(@channel_url)
assert {:error, "Big issue", 1} = VideoCollection.get_video_ids(@channel_url)
end
end
describe "get_source_details/1" do
test "it returns a %SourceDetails{} with data on success" do
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot ->
{:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"}
end)
assert {:ok, res} = VideoCollection.get_source_details(@channel_url)
assert %SourceDetails{id: "UCQH2", name: "TheUselessTrials"} = res
end
test "it passes the expected args to the backend runner" do
expect(YtDlpRunnerMock, :run, fn @channel_url, opts, ot ->
assert opts == [:skip_download, playlist_end: 1]
assert ot == "%(.{channel,channel_id})j"
{:ok, "{}"}
end)
assert {:ok, _} = VideoCollection.get_source_details(@channel_url)
end
test "it returns an error if the runner returns an error" do
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:error, "Big issue", 1} end)
assert {:error, "Big issue", 1} = VideoCollection.get_source_details(@channel_url)
end
test "it returns an error if the output is not JSON" do
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "Not JSON"} end)
assert {:error, %Jason.DecodeError{}} = VideoCollection.get_source_details(@channel_url)
end
end
end