[Enhancement] Allow setting preference for video/audio codec (#255)

* [WIP] started on codec parser

* Added codec preferences to settings model

* [WIP] added basic codec preference fields to settings form

* Hooked up the backend portion of the codec preference work

* Added codec settings to frontend

* Ensured you can remove codec data
This commit is contained in:
Kieran
2024-05-21 16:28:19 -07:00
committed by GitHub
parent dec3938780
commit 81d5efd4c1
12 changed files with 373 additions and 31 deletions
+82
View File
@@ -0,0 +1,82 @@
defmodule Pinchflat.Downloading.CodecParser do
@moduledoc """
Functions for generating yt-dlp codec strings
"""
alias Pinchflat.Settings
@doc """
Generate a video codec string based on the value of the video_codec_preference setting.
Returns binary()
"""
def generate_vcodec_string_from_settings do
generate_vcodec_string(Settings.get!(:video_codec_preference))
end
@doc """
Generate an audio codec string based on the value of the audio_codec_preference setting.
Returns binary()
"""
def generate_acodec_string_from_settings do
generate_acodec_string(Settings.get!(:audio_codec_preference))
end
@doc """
Generate a video codec string from a list of video codecs.
If the list is nil or empty, the default video codec is AVC.
Returns binary()
"""
def generate_vcodec_string(nil), do: "bestvideo[vcodec~='^avc']/bestvideo"
def generate_vcodec_string([]), do: generate_vcodec_string(nil)
def generate_vcodec_string(video_codecs) do
video_codecs
|> Enum.map(&video_codec_map()[&1])
|> Enum.reject(&is_nil/1)
|> Enum.map(&"bestvideo[vcodec~='^#{&1}']")
|> Enum.concat(["bestvideo"])
|> Enum.join("/")
end
@doc """
Generate an audio codec string from a list of audio codecs.
If the list is nil or empty, the default audio codec is MP4A.
Returns binary()
"""
def generate_acodec_string(nil), do: "bestaudio[acodec~='^mp4a']/bestaudio"
def generate_acodec_string([]), do: generate_acodec_string(nil)
def generate_acodec_string(audio_codecs) do
audio_codecs
|> Enum.map(&audio_codec_map()[&1])
|> Enum.reject(&is_nil/1)
|> Enum.map(&"bestaudio[acodec~='^#{&1}']")
|> Enum.concat(["bestaudio"])
|> Enum.join("/")
end
@doc false
def video_codec_map do
%{
"av01" => "av01",
"avc" => "avc",
"vp9" => "vp0?9"
}
end
@doc false
def audio_codec_map do
%{
"aac" => "aac",
"mp4a" => "mp4a",
"mp3" => "mp3",
"opus" => "opus"
}
end
end
@@ -6,6 +6,7 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilder do
alias Pinchflat.Sources
alias Pinchflat.Sources.Source
alias Pinchflat.Media.MediaItem
alias Pinchflat.Downloading.CodecParser
alias Pinchflat.Downloading.OutputPathBuilder
alias Pinchflat.Utils.FilesystemUtils, as: FSUtils
@@ -121,28 +122,26 @@ defmodule Pinchflat.Downloading.DownloadOptionBuilder do
end
defp quality_options(media_profile) do
video_codec_option = fn res ->
[format_sort: "res:#{res},+codec:avc:m4a", remux_video: "mp4"]
end
audio_format_precedence = [
"bestaudio[ext=m4a]",
"bestaudio[ext=mp3]",
"bestaudio",
"best[ext=m4a]",
"best[ext=mp3]",
"best"
]
vcodec_string = CodecParser.generate_vcodec_string_from_settings()
acodec_string = CodecParser.generate_acodec_string_from_settings()
case media_profile.preferred_resolution do
# Also be aware that :audio disabled all embedding options for subtitles
:audio -> [:extract_audio, format: Enum.join(audio_format_precedence, "/")]
:"360p" -> video_codec_option.("360")
:"480p" -> video_codec_option.("480")
:"720p" -> video_codec_option.("720")
:"1080p" -> video_codec_option.("1080")
:"2160p" -> video_codec_option.("2160")
:"4320p" -> video_codec_option.("4320")
:audio ->
[:extract_audio, format: "#{acodec_string}/best"]
resolution_atom ->
{resolution_string, _} =
resolution_atom
|> Atom.to_string()
|> Integer.parse()
[
format_sort: "res:#{resolution_string}",
# Since Plex doesn't support reading metadata from MKV
remux_video: "mp4",
format: "((#{vcodec_string})+(#{acodec_string}))/best"
]
end
end
+39 -1
View File
@@ -11,7 +11,14 @@ defmodule Pinchflat.Settings.Setting do
:pro_enabled,
:yt_dlp_version,
:apprise_version,
:apprise_server
:apprise_server,
:video_codec_preference,
:audio_codec_preference
]
@virtual_fields [
:video_codec_preference_string,
:audio_codec_preference_string
]
@required_fields ~w(
@@ -25,12 +32,43 @@ defmodule Pinchflat.Settings.Setting do
field :yt_dlp_version, :string
field :apprise_version, :string
field :apprise_server, :string
field :video_codec_preference, {:array, :string}, default: []
field :audio_codec_preference, {:array, :string}, default: []
field :video_codec_preference_string, :string, default: nil, virtual: true
field :audio_codec_preference_string, :string, default: nil, virtual: true
end
@doc false
def changeset(setting, attrs) do
setting
|> cast(attrs, @allowed_fields)
|> cast(attrs, @virtual_fields, empty_values: [])
|> convert_codec_preference_strings()
|> validate_required(@required_fields)
end
defp convert_codec_preference_strings(changeset) do
fields = [
video_codec_preference_string: :video_codec_preference,
audio_codec_preference_string: :audio_codec_preference
]
Enum.reduce(fields, changeset, fn {virtual_field, actual_field}, changeset ->
case get_change(changeset, virtual_field) do
nil ->
changeset
value ->
new_value =
value
|> String.split(">")
|> Enum.map(&String.trim/1)
|> Enum.reject(&(String.trim(&1) == ""))
|> Enum.map(&String.downcase/1)
put_change(changeset, actual_field, new_value)
end
end)
end
end