Added nfo builder for 'episodes'
This commit is contained in:
parent
cf59bf99cd
commit
b8add0201e
7 changed files with 158 additions and 13 deletions
|
|
@ -15,12 +15,25 @@ defmodule Pinchflat.Filesystem.FilesystemHelpers do
|
||||||
tmpfile_directory = Application.get_env(:pinchflat, :tmpfile_directory)
|
tmpfile_directory = Application.get_env(:pinchflat, :tmpfile_directory)
|
||||||
filepath = Path.join([tmpfile_directory, "#{StringUtils.random_string(64)}.#{type}"])
|
filepath = Path.join([tmpfile_directory, "#{StringUtils.random_string(64)}.#{type}"])
|
||||||
|
|
||||||
:ok = File.mkdir_p!(Path.dirname(filepath))
|
:ok = write_p!(filepath, "")
|
||||||
:ok = File.write(filepath, "")
|
|
||||||
|
|
||||||
filepath
|
filepath
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Writes content to a file, creating directories as needed.
|
||||||
|
Takes the same args as File.write!/3.
|
||||||
|
|
||||||
|
Returns :ok | raises on error
|
||||||
|
"""
|
||||||
|
def write_p!(filepath, content, modes \\ []) do
|
||||||
|
filepath
|
||||||
|
|> Path.dirname()
|
||||||
|
|> File.mkdir_p!()
|
||||||
|
|
||||||
|
File.write!(filepath, content, modes)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Fetches the file size of a media item and saves it to the database.
|
Fetches the file size of a media item and saves it to the database.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ defmodule Pinchflat.Metadata.MetadataFileHelpers do
|
||||||
needed
|
needed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
alias Pinchflat.Filesystem.FilesystemHelpers
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Compresses and stores metadata for a media item, returning the filepath.
|
Compresses and stores metadata for a media item, returning the filepath.
|
||||||
|
|
||||||
|
|
@ -18,8 +20,7 @@ defmodule Pinchflat.Metadata.MetadataFileHelpers do
|
||||||
filepath = generate_filepath_for(database_record, "metadata.json.gz")
|
filepath = generate_filepath_for(database_record, "metadata.json.gz")
|
||||||
{:ok, json} = Phoenix.json_library().encode(metadata_map)
|
{:ok, json} = Phoenix.json_library().encode(metadata_map)
|
||||||
|
|
||||||
File.mkdir_p!(Path.dirname(filepath))
|
:ok = FilesystemHelpers.write_p!(filepath, json, [:compressed])
|
||||||
:ok = File.write(filepath, json, [:compressed])
|
|
||||||
|
|
||||||
filepath
|
filepath
|
||||||
end
|
end
|
||||||
|
|
@ -45,12 +46,23 @@ defmodule Pinchflat.Metadata.MetadataFileHelpers do
|
||||||
filepath = generate_filepath_for(database_record, Path.basename(thumbnail_url))
|
filepath = generate_filepath_for(database_record, Path.basename(thumbnail_url))
|
||||||
thumbnail_blob = fetch_thumbnail_from_url(thumbnail_url)
|
thumbnail_blob = fetch_thumbnail_from_url(thumbnail_url)
|
||||||
|
|
||||||
File.mkdir_p!(Path.dirname(filepath))
|
:ok = FilesystemHelpers.write_p!(filepath, thumbnail_blob)
|
||||||
:ok = File.write(filepath, thumbnail_blob)
|
|
||||||
|
|
||||||
filepath
|
filepath
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Parses an upload date from the YYYYMMDD string returned in yt-dlp metadata
|
||||||
|
and returns a Date struct.
|
||||||
|
|
||||||
|
Returns Date.t()
|
||||||
|
"""
|
||||||
|
def parse_upload_date(upload_date) do
|
||||||
|
<<year::binary-size(4)>> <> <<month::binary-size(2)>> <> <<day::binary-size(2)>> = upload_date
|
||||||
|
|
||||||
|
Date.from_iso8601!("#{year}-#{month}-#{day}")
|
||||||
|
end
|
||||||
|
|
||||||
defp fetch_thumbnail_from_url(url) do
|
defp fetch_thumbnail_from_url(url) do
|
||||||
http_client = Application.get_env(:pinchflat, :http_client, Pinchflat.HTTP.HTTPClient)
|
http_client = Application.get_env(:pinchflat, :http_client, Pinchflat.HTTP.HTTPClient)
|
||||||
{:ok, body} = http_client.get(url, [], body_format: :binary)
|
{:ok, body} = http_client.get(url, [], body_format: :binary)
|
||||||
|
|
|
||||||
44
lib/pinchflat/metadata/nfo_builder.ex
Normal file
44
lib/pinchflat/metadata/nfo_builder.ex
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
defmodule Pinchflat.Metadata.NfoBuilder do
|
||||||
|
@moduledoc """
|
||||||
|
Provides methods for building and storing NFO files for
|
||||||
|
use by Kodi/Jellyfin and other media center software.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Pinchflat.Metadata.MetadataFileHelpers
|
||||||
|
alias Pinchflat.Filesystem.FilesystemHelpers
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Builds an NFO file for a media item (read: single "episode") and
|
||||||
|
stores it in the same directory as the media file. Has the same name
|
||||||
|
as the media file, but with a .nfo extension.
|
||||||
|
|
||||||
|
Returns the filepath of the NFO file.
|
||||||
|
"""
|
||||||
|
def build_and_store_for_media_item(metadata) do
|
||||||
|
filepath = Path.rootname(metadata["filepath"]) <> ".nfo"
|
||||||
|
nfo = build_for_media_item(metadata)
|
||||||
|
|
||||||
|
FilesystemHelpers.write_p!(filepath, nfo)
|
||||||
|
|
||||||
|
filepath
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_for_media_item(metadata) do
|
||||||
|
upload_date = MetadataFileHelpers.parse_upload_date(metadata["upload_date"])
|
||||||
|
# Cribbed from a combination of the Kodi wiki, ytdl-nfo, and ytdl-sub.
|
||||||
|
# WHO NEEDS A FANCY XML PARSER ANYWAY?!
|
||||||
|
"""
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||||
|
<episodedetails>
|
||||||
|
<title>#{metadata["title"]}</title>
|
||||||
|
<showtitle>#{metadata["uploader"]}</showtitle>
|
||||||
|
<uniqueid type="youtube" default="true">#{metadata["id"]}</uniqueid>
|
||||||
|
<plot>#{metadata["description"]}</plot>
|
||||||
|
<premiered>#{upload_date}</premiered>
|
||||||
|
<season>#{upload_date.year}</season>
|
||||||
|
<episode>#{Calendar.strftime(upload_date, "%m%d")}</episode>
|
||||||
|
<genre>YouTube</genre>
|
||||||
|
</episodedetails>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -25,6 +25,7 @@ defmodule Pinchflat.YtDlp.Media do
|
||||||
|
|
||||||
alias __MODULE__
|
alias __MODULE__
|
||||||
alias Pinchflat.Utils.FunctionUtils
|
alias Pinchflat.Utils.FunctionUtils
|
||||||
|
alias Pinchflat.Metadata.MetadataFileHelpers
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Downloads a single piece of media (and possibly its metadata) directly to its
|
Downloads a single piece of media (and possibly its metadata) directly to its
|
||||||
|
|
@ -86,7 +87,7 @@ defmodule Pinchflat.YtDlp.Media do
|
||||||
original_url: response["webpage_url"],
|
original_url: response["webpage_url"],
|
||||||
livestream: response["was_live"],
|
livestream: response["was_live"],
|
||||||
short_form_content: response["webpage_url"] && short_form_content?(response),
|
short_form_content: response["webpage_url"] && short_form_content?(response),
|
||||||
upload_date: response["upload_date"] && parse_upload_date(response["upload_date"])
|
upload_date: response["upload_date"] && MetadataFileHelpers.parse_upload_date(response["upload_date"])
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -106,12 +107,6 @@ defmodule Pinchflat.YtDlp.Media do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_upload_date(upload_date) do
|
|
||||||
<<year::binary-size(4)>> <> <<month::binary-size(2)>> <> <<day::binary-size(2)>> = upload_date
|
|
||||||
|
|
||||||
Date.from_iso8601!("#{year}-#{month}-#{day}")
|
|
||||||
end
|
|
||||||
|
|
||||||
defp backend_runner do
|
defp backend_runner do
|
||||||
# This approach lets us mock the command for testing
|
# This approach lets us mock the command for testing
|
||||||
Application.get_env(:pinchflat, :yt_dlp_runner)
|
Application.get_env(:pinchflat, :yt_dlp_runner)
|
||||||
|
|
|
||||||
|
|
@ -33,4 +33,27 @@ defmodule Pinchflat.Filesystem.FilesystemHelpersTest do
|
||||||
assert {:error, _} = FilesystemHelpers.compute_and_save_media_filesize(media_item)
|
assert {:error, _} = FilesystemHelpers.compute_and_save_media_filesize(media_item)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "write_p!/3" do
|
||||||
|
test "writes content to a file" do
|
||||||
|
filepath = FilesystemHelpers.generate_metadata_tmpfile(:json)
|
||||||
|
content = "{}"
|
||||||
|
|
||||||
|
assert :ok = FilesystemHelpers.write_p!(filepath, content)
|
||||||
|
assert File.read!(filepath) == content
|
||||||
|
|
||||||
|
File.rm!(filepath)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "creates directories as needed" do
|
||||||
|
tmpfile_directory = Application.get_env(:pinchflat, :tmpfile_directory)
|
||||||
|
filepath = Path.join([tmpfile_directory, "foo", "bar", "file.json"])
|
||||||
|
content = "{}"
|
||||||
|
|
||||||
|
assert :ok = FilesystemHelpers.write_p!(filepath, content)
|
||||||
|
assert File.read!(filepath) == content
|
||||||
|
|
||||||
|
File.rm!(filepath)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -84,4 +84,12 @@ defmodule Pinchflat.Metadata.MetadataFileHelpersTest do
|
||||||
assert Path.basename(filepath) == "maxres.webp"
|
assert Path.basename(filepath) == "maxres.webp"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "parse_upload_date/1" do
|
||||||
|
test "returns a date from the given metadata upload date" do
|
||||||
|
upload_date = "20210101"
|
||||||
|
|
||||||
|
assert Helpers.parse_upload_date(upload_date) == ~D[2021-01-01]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
50
test/pinchflat/metadata/nfo_builder_test.exs
Normal file
50
test/pinchflat/metadata/nfo_builder_test.exs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
defmodule Pinchflat.Metadata.NfoBuilderTest do
|
||||||
|
use Pinchflat.DataCase
|
||||||
|
|
||||||
|
alias Pinchflat.Metadata.NfoBuilder
|
||||||
|
|
||||||
|
setup do
|
||||||
|
json_filepath =
|
||||||
|
Path.join([
|
||||||
|
File.cwd!(),
|
||||||
|
"test",
|
||||||
|
"support",
|
||||||
|
"files",
|
||||||
|
"media_metadata.json"
|
||||||
|
])
|
||||||
|
|
||||||
|
{:ok, file_body} = File.read(json_filepath)
|
||||||
|
{:ok, parsed_json} = Phoenix.json_library().decode(file_body)
|
||||||
|
|
||||||
|
{:ok, %{metadata: parsed_json}}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "build_and_store_for_media_item/1" do
|
||||||
|
test "returns the filepath", %{metadata: metadata} do
|
||||||
|
result = NfoBuilder.build_and_store_for_media_item(metadata)
|
||||||
|
|
||||||
|
assert File.exists?(result)
|
||||||
|
|
||||||
|
File.rm!(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "builds filepath based on media location", %{metadata: metadata} do
|
||||||
|
result = NfoBuilder.build_and_store_for_media_item(metadata)
|
||||||
|
|
||||||
|
assert String.contains?(result, Path.rootname(metadata["filepath"]))
|
||||||
|
assert String.ends_with?(result, ".nfo")
|
||||||
|
|
||||||
|
File.rm!(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "builds an NFO file", %{metadata: metadata} do
|
||||||
|
result = NfoBuilder.build_and_store_for_media_item(metadata)
|
||||||
|
nfo = File.read!(result)
|
||||||
|
|
||||||
|
assert String.contains?(nfo, ~S(<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>))
|
||||||
|
assert String.contains?(nfo, "<title>#{metadata["title"]}</title>")
|
||||||
|
|
||||||
|
File.rm!(result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue