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

This commit is contained in:
Kieran Eglin 2024-01-20 22:02:18 -08:00
parent f6fd0775d9
commit fad1e39bad
No known key found for this signature in database
GPG key ID: 193984967FCF432D
5 changed files with 161 additions and 9 deletions

View file

@ -10,6 +10,10 @@ defmodule Pinchflat.Downloader.Backends.YtDlp.CommandRunner do
@doc """
Runs a yt-dlp command and returns the string output
# TODO: deduplicate command opts, keeping the last one on conflict
although possibly not needed (and a LOT easier) if yt-dlp
just ignores duplicate options (ie: look into that)
"""
@impl BackendCommandRunner
def run(url, command_opts) do

View file

@ -10,6 +10,7 @@ defmodule Pinchflat.Downloader.Backends.YtDlp.Video do
The video will be moved to its final destination... elsewhere
# TODO: update these docs when I figure out a module to move videos
# TODO: test
# NOTE: maybe instead of moving it to the tempdir, I can just download it
to the final destination by using the `output` option. The
parser could be updated to generate a value for the output option.
@ -17,15 +18,9 @@ defmodule Pinchflat.Downloader.Backends.YtDlp.Video do
newer users can use the easier liquid-like syntax.
"""
def download(url, command_opts \\ []) do
default_output_path = Path.join([base_directory(), "%(id)s", "%(id)s.%(ext)s"])
default_opts = [output: default_output_path]
opts = Keyword.merge(default_opts, command_opts)
backend_runner().run(url, opts)
end
defp base_directory do
Application.get_env(:pinchflat, :media_directory)
# TODO: if this stays this simple, consider not abstracting it
# HOWEVER - this module does provide clarity of intent so maybe keep?
backend_runner().run(url, command_opts)
end
defp backend_runner do

View file

@ -0,0 +1,39 @@
defmodule Pinchflat.Downloader.VideoDownloader do
@moduledoc """
This is the integration layer for actually downloading videos.
It takes into account the media profile's settings in order
to download the video with the desired options.
Technically hardcodes the yt-dlp backend for now, but should leave
it open-ish for future expansion (just in case).
"""
alias Pinchflat.Downloader.Backends.YtDlp.Video, as: YtDlpVideo
alias Pinchflat.Profiles.Options.YtDlp.OptionBuilder, as: YtDlpOptionBuilder
@doc """
Downloads a single video based on the settings in the given media profile.
# TODO: implement media profiles - so far this is a glorified mock
# TODO: test
"""
def download_for_media_profile(url, media_profile, backend \\ :yt_dlp) do
option_builder = option_builder(backend)
video_backend = video_backend(backend)
{:ok, options} = option_builder.build(media_profile)
video_backend.download(url, options)
end
def option_builder(backend) do
case backend do
:yt_dlp -> YtDlpOptionBuilder
end
end
def video_backend(backend) do
case backend do
:yt_dlp -> YtDlpVideo
end
end
end

View file

@ -0,0 +1,43 @@
defmodule Pinchflat.Profiles.Options.YtDlp.OptionBuilder do
@moduledoc """
Builds the options for yt-dlp based on the given media profile.
TODO: probably make this a behaviour so I can add other backends later
"""
alias Pinchflat.Profiles.Options.YtDlp.OutputPathBuilder
@doc """
Builds the options for yt-dlp based on the given media profile.
TODO: add a guard to ensure argument is a media profile
TODO: consider adding the ability to pass in a second argument to override
these options
TODO: test
"""
def build(media_profile) do
{:ok, output_path} = OutputPathBuilder.build(media_profile.output_path_template)
# NOTE: I'll be hardcoding most things for now (esp. options to help me test) -
# add more configuration later as I build out the models. Walk before you can run!
{:ok,
[
:write_thumbnail,
:write_subs,
:embed_metadata,
:embed_thumbnail,
:embed_subs,
:write_info_json,
:write_auto_subs,
:no_progress,
convert_thumbnails: "jpg",
sub_langs: "en.*",
output: Path.join(base_directory(), output_path)
]}
end
defp base_directory do
Application.get_env(:pinchflat, :media_directory)
end
end

View file

@ -0,0 +1,71 @@
defmodule Pinchflat.Profiles.Options.YtDlp.OutputPathBuilder do
@moduledoc """
Builds yt-dlp-friendly output paths for downloaded media
TODO: probably make this a behaviour so I can add other backends later
"""
alias Pinchflat.RenderedString.Parser, as: TemplateParser
@doc """
Builds the actual final filepath from a given template.
Translates liquid-style templates into yt-dlp-style templates,
leaving yt-dlp syntax intact.
TODO: test
"""
def build(template_string) do
TemplateParser.parse(template_string, full_yt_dlp_options_map())
end
defp full_yt_dlp_options_map do
Map.merge(
standard_yt_dlp_option_map(),
custom_yt_dlp_option_map()
)
end
defp standard_yt_dlp_option_map do
%{
"id" => "%(id)s",
"ext" => "%(ext)s",
"title" => "%(title)s",
"fulltitle" => "%(fulltitle)s",
"uploader" => "%(uploader)s",
"creator" => "%(creator)s",
"upload_date" => "%(upload_date)s",
"release_date" => "%(release_date)s",
"duration" => "%(duration)s",
# For videos classified as an episode of a series:
"series" => "%(series)s",
"season" => "%(season)s",
"season_number" => "%(season_number)s",
"episode" => "%(episode)s",
"episode_number" => "%(episode_number)s",
"episode_id" => "%(episode_id)s",
# For videos classified as music:
"track" => "%(track)s",
"track_number" => "%(track_number)s",
"artist" => "%(artist)s",
"album" => "%(album)s",
"album_type" => "%(album_type)s",
"genre" => "%(genre)s"
}
end
defp custom_yt_dlp_option_map do
%{
# Filepath-safe versions of some standard options
"safe_id" => "%(id)S",
"safe_title" => "%(title)S",
"safe_fulltitle" => "%(fulltitle)S",
"safe_uploader" => "%(uploader)S",
"safe_creator" => "%(creator)S",
# Individual parts of the upload date
"upload_year" => "%(upload_date>%Y)s",
"upload_month" => "%(upload_date>%m)s",
"upload_day" => "%(upload_date>%d)s"
}
end
end