Download video(s) (first iteration) (#5)

* Updated package.json (and made an excuse to make a branch)

* Video filepath parser (#6)

* Restructured files; Added parser placeholder

* More restructuring

* Added basic parser for hydrating template strings

* Improved docs

* More docs

* Initial implementation of media profiles (#7)

* [WIP] Added basic video download method

* [WIP] Very-WIP first steps at parsing options and downloading

* Made my options safe by default and removed special safe versions

* Ran html generator for mediaprofile model - leaving as-is for now

* Addressed a bunch of TODO comments

* Add "channel" type Media Source (#8)

* [WIP] Working on fetching channel metadata in yt-dlp backend

* Finished first draft of methods to do with querying channels

* Renamed CommandRunnerMock to have a more descriptive name

* Ran the phx generator for the channel model

* Renamed Downloader namespace to MediaClient

* [WIP] saving before attempting LiveView

* LiveView did not work out but here's a working controller how about

* Index a channel (#9)

* Ran a MediaItem generator; Reformatted to my liking

* [WIP] added basic index function

* setup oban

* Added basic Oban job for indexing

* Added in workers for indexing; hooked them into record creation flow

* Added a task model with a phx generator

* Tied together tasks with jobs and channels

* Download indexed videos (#10)

* Clarified documentation

* more comments

* [WIP] hooked up basic video downloading; starting work on metadata

* Added metadata model and parsing

Adding the metadata model made me realize that, in many cases, yt-dlp
returns undesired input in stdout, breaking parsing. In order to get
the metadata model working, I had to change the way in which the app
interacts with yt-dlp. Now, output is written as a file to disk which
is immediately re-read and returned.

* Added tests for video download worker

* Hooked up video downloading to the channel indexing pipeline

* Adds tasks for media items

* Updated video metadata parser to extract the title

* Ran linting
This commit is contained in:
Kieran
2024-01-30 20:15:59 -08:00
committed by GitHub
parent fee39151a8
commit a5e7c48f70
96 changed files with 9358 additions and 158 deletions
+1
View File
@@ -28,6 +28,7 @@ defmodule PinchflatWeb.ConnCase do
import Plug.Conn
import Phoenix.ConnTest
import PinchflatWeb.ConnCase
import Pinchflat.TestingHelperMethods
end
end
+3
View File
@@ -20,10 +20,13 @@ defmodule Pinchflat.DataCase do
quote do
alias Pinchflat.Repo
use Oban.Testing, repo: Repo
import Ecto
import Ecto.Changeset
import Ecto.Query
import Pinchflat.DataCase
import Pinchflat.TestingHelperMethods
end
end
File diff suppressed because one or more lines are too long
+19
View File
@@ -0,0 +1,19 @@
defmodule Pinchflat.JobFixtures do
@moduledoc false
defmodule TestJobWorker do
@moduledoc false
use Oban.Worker, queue: :default
@impl Oban.Worker
def perform(%Oban.Job{}) do
:ok
end
end
def job_fixture() do
{:ok, job} = Oban.insert(TestJobWorker.new(%{}))
job
end
end
+45
View File
@@ -0,0 +1,45 @@
defmodule Pinchflat.MediaFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `Pinchflat.Media` context.
"""
alias Pinchflat.MediaSourceFixtures
@doc """
Generate a media_item.
"""
def media_item_fixture(attrs \\ %{}) do
{:ok, media_item} =
attrs
|> Enum.into(%{
media_id: Faker.String.base64(12),
title: Faker.Commerce.product_name(),
video_filepath: "/video/#{Faker.File.file_name(:video)}",
channel_id: MediaSourceFixtures.channel_fixture().id
})
|> Pinchflat.Media.create_media_item()
media_item
end
@doc """
Generate a media_item with metadata.
"""
def media_item_with_metadata(attrs \\ %{}) do
json_filepath =
Path.join([
Path.dirname(__ENV__.file),
"support",
"fixtures",
"files",
"media_metadata.json"
])
{:ok, file_body} = File.read(json_filepath)
{:ok, parsed_json} = Phoenix.json_library().decode(file_body)
merged_attrs = Map.merge(attrs, %{metadata: %{client_respinse: parsed_json}})
media_item_fixture(merged_attrs)
end
end
@@ -0,0 +1,30 @@
defmodule Pinchflat.MediaSourceFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `Pinchflat.MediaSource` context.
"""
alias Pinchflat.Repo
alias Pinchflat.ProfilesFixtures
alias Pinchflat.MediaSource.Channel
@doc """
Generate a channel.
"""
def channel_fixture(attrs \\ %{}) do
{:ok, channel} =
%Channel{}
|> Channel.changeset(
Enum.into(attrs, %{
name: "Channel ##{:rand.uniform(1_000_000)}",
channel_id: Base.encode16(:crypto.hash(:md5, "#{:rand.uniform(1_000_000)}")),
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}",
media_profile_id: ProfilesFixtures.media_profile_fixture().id,
index_frequency_minutes: 60
})
)
|> Repo.insert()
channel
end
end
@@ -0,0 +1,21 @@
defmodule Pinchflat.ProfilesFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `Pinchflat.Profiles` context.
"""
@doc """
Generate a media_profile.
"""
def media_profile_fixture(attrs \\ %{}) do
{:ok, media_profile} =
attrs
|> Enum.into(%{
name: "Media Profile ##{:rand.uniform(1_000_000)}",
output_path_template: "{{title}}.{{ext}}"
})
|> Pinchflat.Profiles.create_media_profile()
media_profile
end
end
+24
View File
@@ -0,0 +1,24 @@
defmodule Pinchflat.TasksFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `Pinchflat.Tasks` context.
"""
alias Pinchflat.JobFixtures
alias Pinchflat.MediaSourceFixtures
@doc """
Generate a task.
"""
def task_fixture(attrs \\ %{}) do
{:ok, task} =
attrs
|> Enum.into(%{
channel_id: MediaSourceFixtures.channel_fixture().id,
job_id: JobFixtures.job_fixture().id
})
|> Pinchflat.Tasks.create_task()
task
end
end
+14 -5
View File
@@ -1,7 +1,16 @@
#!/bin/bash
if [[ "$@" == *"--dump-json"* ]]; then
echo '{ "args": "'$@'"}'
else
echo $@
fi
# Args come in the format of "<unknown number of args> --print-to-file <output template> <file location> <unknown number of args>".
# I need to extract <file location> and write all args to it.
# Extract the file location (in an unknown position BUT it's 2 args after --print-to-file).
for ((i = 1; i <= $#; i++)); do
if [ "${!i}" == "--print-to-file" ]; then
# Extract the file location.
file_location="${@:i+2:1}"
break
fi
done
# Write all args to the file
echo "$@" >"$file_location"
+43
View File
@@ -0,0 +1,43 @@
defmodule Pinchflat.TestingHelperMethods do
@moduledoc false
use ExUnit.CaseTemplate
def now do
DateTime.utc_now()
end
def now_plus(offset, unit) when unit in [:minute, :minutes] do
DateTime.add(now(), offset, :minute)
end
def assert_changed(checker_fun, action_fn) do
before_res = checker_fun.()
action_fn.()
after_res = checker_fun.()
assert before_res != after_res
end
def assert_changed([from: from, to: to], checker_fun, action_fn) do
before_res = checker_fun.()
action_fn.()
after_res = checker_fun.()
assert before_res == from
assert after_res == to
end
def render_metadata(metadata_name) do
json_filepath =
Path.join([
File.cwd!(),
"test",
"support",
"files",
"#{metadata_name}.json"
])
File.read!(json_filepath)
end
end