Hooked up NFO generation to the source worker
This commit is contained in:
parent
faf4f2e7e4
commit
c9e576a2d6
7 changed files with 165 additions and 13 deletions
|
|
@ -21,6 +21,20 @@ defmodule Pinchflat.Metadata.NfoBuilder do
|
||||||
filepath
|
filepath
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Builds an NFO file for a souce and stores it at the specified location.
|
||||||
|
Technically works for playlists, but it's really made for channels.
|
||||||
|
|
||||||
|
Returns the filepath of the NFO file.
|
||||||
|
"""
|
||||||
|
def build_and_store_for_source(filepath, metadata) do
|
||||||
|
nfo = build_for_source(metadata)
|
||||||
|
|
||||||
|
FilesystemHelpers.write_p!(filepath, nfo)
|
||||||
|
|
||||||
|
filepath
|
||||||
|
end
|
||||||
|
|
||||||
defp build_for_media_item(metadata) do
|
defp build_for_media_item(metadata) do
|
||||||
upload_date = MetadataFileHelpers.parse_upload_date(metadata["upload_date"])
|
upload_date = MetadataFileHelpers.parse_upload_date(metadata["upload_date"])
|
||||||
# Cribbed from a combination of the Kodi wiki, ytdl-nfo, and ytdl-sub.
|
# Cribbed from a combination of the Kodi wiki, ytdl-nfo, and ytdl-sub.
|
||||||
|
|
@ -39,4 +53,16 @@ defmodule Pinchflat.Metadata.NfoBuilder do
|
||||||
</episodedetails>
|
</episodedetails>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp build_for_source(metadata) do
|
||||||
|
"""
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||||
|
<tvshow>
|
||||||
|
<title>#{metadata["title"]}</title>
|
||||||
|
<plot>#{metadata["description"]}</plot>
|
||||||
|
<uniqueid type="youtube" default="true">#{metadata["id"]}</uniqueid>
|
||||||
|
<genre>YouTube</genre>
|
||||||
|
</tvshow>
|
||||||
|
"""
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
|
||||||
use Oban.Worker,
|
use Oban.Worker,
|
||||||
queue: :remote_metadata,
|
queue: :remote_metadata,
|
||||||
tags: ["media_source", "source_metadata", "remote_metadata"],
|
tags: ["media_source", "source_metadata", "remote_metadata"],
|
||||||
max_attempts: 1,
|
max_attempts: 3,
|
||||||
# This is the only thing stopping this job from calling itself
|
# This is the only thing stopping this job from calling itself
|
||||||
# in an infinite loop. Time is in seconds
|
# in an infinite loop. Time is in seconds
|
||||||
unique: [period: 120]
|
unique: [period: 120]
|
||||||
|
|
@ -15,6 +15,7 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
|
||||||
alias Pinchflat.Repo
|
alias Pinchflat.Repo
|
||||||
alias Pinchflat.Tasks
|
alias Pinchflat.Tasks
|
||||||
alias Pinchflat.Sources
|
alias Pinchflat.Sources
|
||||||
|
alias Pinchflat.Metadata.NfoBuilder
|
||||||
alias Pinchflat.YtDlp.MediaCollection
|
alias Pinchflat.YtDlp.MediaCollection
|
||||||
alias Pinchflat.Metadata.MetadataFileHelpers
|
alias Pinchflat.Metadata.MetadataFileHelpers
|
||||||
alias Pinchflat.Downloading.DownloadOptionBuilder
|
alias Pinchflat.Downloading.DownloadOptionBuilder
|
||||||
|
|
@ -41,6 +42,7 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
|
||||||
@impl Oban.Worker
|
@impl Oban.Worker
|
||||||
def perform(%Oban.Job{args: %{"id" => source_id}}) do
|
def perform(%Oban.Job{args: %{"id" => source_id}}) do
|
||||||
source = Repo.preload(Sources.get_source!(source_id), [:metadata, :media_profile])
|
source = Repo.preload(Sources.get_source!(source_id), [:metadata, :media_profile])
|
||||||
|
source_metadata = fetch_source_metadata(source)
|
||||||
series_directory = determine_series_directory(source)
|
series_directory = determine_series_directory(source)
|
||||||
|
|
||||||
# Since updating a source kicks this job off again, we enforce job uniqueness (above)
|
# Since updating a source kicks this job off again, we enforce job uniqueness (above)
|
||||||
|
|
@ -48,8 +50,9 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
|
||||||
# in an infinite loop.
|
# in an infinite loop.
|
||||||
Sources.update_source(source, %{
|
Sources.update_source(source, %{
|
||||||
series_directory: series_directory,
|
series_directory: series_directory,
|
||||||
|
nfo_filepath: store_source_nfo(source, series_directory, source_metadata),
|
||||||
metadata: %{
|
metadata: %{
|
||||||
metadata_filepath: store_source_metadata(source)
|
metadata_filepath: store_source_metadata(source, source_metadata)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -59,9 +62,13 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
|
||||||
Ecto.StaleEntryError -> Logger.info("#{__MODULE__} discarded: source #{source_id} stale")
|
Ecto.StaleEntryError -> Logger.info("#{__MODULE__} discarded: source #{source_id} stale")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp store_source_metadata(source) do
|
defp fetch_source_metadata(source) do
|
||||||
{:ok, metadata} = MediaCollection.get_source_metadata(source.original_url)
|
{:ok, metadata} = MediaCollection.get_source_metadata(source.original_url)
|
||||||
|
|
||||||
|
metadata
|
||||||
|
end
|
||||||
|
|
||||||
|
defp store_source_metadata(source, metadata) do
|
||||||
MetadataFileHelpers.compress_and_store_metadata_for(source, metadata)
|
MetadataFileHelpers.compress_and_store_metadata_for(source, metadata)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -74,4 +81,12 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorker do
|
||||||
{:error, _} -> nil
|
{:error, _} -> nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp store_source_nfo(source, series_directory, metadata) do
|
||||||
|
if source.download_nfo && series_directory do
|
||||||
|
nfo_filepath = Path.join(series_directory, "tvshow.nfo")
|
||||||
|
|
||||||
|
NfoBuilder.build_and_store_for_source(nfo_filepath, metadata)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -106,4 +106,9 @@ defmodule Pinchflat.Sources.Source do
|
||||||
# minutes
|
# minutes
|
||||||
15
|
15
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def filepath_attributes do
|
||||||
|
~w(nfo_filepath)a
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,6 @@ defmodule Pinchflat.Sources do
|
||||||
"""
|
"""
|
||||||
def delete_source(%Source{} = source, opts \\ []) do
|
def delete_source(%Source{} = source, opts \\ []) do
|
||||||
delete_files = Keyword.get(opts, :delete_files, false)
|
delete_files = Keyword.get(opts, :delete_files, false)
|
||||||
|
|
||||||
Tasks.delete_tasks_for(source)
|
Tasks.delete_tasks_for(source)
|
||||||
|
|
||||||
source
|
source
|
||||||
|
|
@ -111,7 +110,11 @@ defmodule Pinchflat.Sources do
|
||||||
Media.delete_media_item(media_item, delete_files: delete_files)
|
Media.delete_media_item(media_item, delete_files: delete_files)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
delete_source_metadata_files(source)
|
if delete_files do
|
||||||
|
delete_source_files(source)
|
||||||
|
end
|
||||||
|
|
||||||
|
delete_internal_metadata_files(source)
|
||||||
Repo.delete(source)
|
Repo.delete(source)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -134,16 +137,23 @@ defmodule Pinchflat.Sources do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp delete_source_metadata_files(source) do
|
defp delete_source_files(source) do
|
||||||
|
mapped_struct = Map.from_struct(source)
|
||||||
|
|
||||||
|
Source.filepath_attributes()
|
||||||
|
|> Enum.map(fn field -> mapped_struct[field] end)
|
||||||
|
|> Enum.filter(&is_binary/1)
|
||||||
|
|> Enum.each(&FilesystemHelpers.delete_file_and_remove_empty_directories/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp delete_internal_metadata_files(source) do
|
||||||
metadata = Repo.preload(source, :metadata).metadata || %SourceMetadata{}
|
metadata = Repo.preload(source, :metadata).metadata || %SourceMetadata{}
|
||||||
mapped_struct = Map.from_struct(metadata)
|
mapped_struct = Map.from_struct(metadata)
|
||||||
|
|
||||||
filepaths =
|
SourceMetadata.filepath_attributes()
|
||||||
SourceMetadata.filepath_attributes()
|
|> Enum.map(fn field -> mapped_struct[field] end)
|
||||||
|> Enum.map(fn field -> mapped_struct[field] end)
|
|> Enum.filter(&is_binary/1)
|
||||||
|> Enum.filter(&is_binary/1)
|
|> Enum.each(&FilesystemHelpers.delete_file_and_remove_empty_directories/1)
|
||||||
|
|
||||||
Enum.each(filepaths, &FilesystemHelpers.delete_file_and_remove_empty_directories/1)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_source_details_to_changeset(source, changeset) do
|
defp add_source_details_to_changeset(source, changeset) do
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,20 @@ defmodule Pinchflat.Metadata.NfoBuilderTest do
|
||||||
assert String.contains?(nfo, "<title>#{metadata["title"]}</title>")
|
assert String.contains?(nfo, "<title>#{metadata["title"]}</title>")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "build_and_store_for_source/2" do
|
||||||
|
test "returns the filepath", %{metadata: metadata, filepath: filepath} do
|
||||||
|
result = NfoBuilder.build_and_store_for_source(filepath, metadata)
|
||||||
|
|
||||||
|
assert File.exists?(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "builds an NFO file", %{metadata: metadata, filepath: filepath} do
|
||||||
|
result = NfoBuilder.build_and_store_for_source(filepath, 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>")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -138,4 +138,64 @@ defmodule Pinchflat.Metadata.SourceMetadataStorageWorkerTest do
|
||||||
refute source.series_directory
|
refute source.series_directory
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "perform/1 when storing the series NFO" do
|
||||||
|
test "stores the NFO if specified" do
|
||||||
|
stub(YtDlpRunnerMock, :run, fn
|
||||||
|
_url, _opts, ot when ot == @source_details_ot ->
|
||||||
|
filename = Path.join([Application.get_env(:pinchflat, :media_directory), "Season 1", "bar.mp4"])
|
||||||
|
|
||||||
|
{:ok, source_details_return_fixture(%{filename: filename})}
|
||||||
|
|
||||||
|
_url, _opts, ot when ot == @metadata_ot ->
|
||||||
|
{:ok, "{}"}
|
||||||
|
end)
|
||||||
|
|
||||||
|
source = source_fixture(%{download_nfo: true, nfo_filepath: nil})
|
||||||
|
perform_job(SourceMetadataStorageWorker, %{id: source.id})
|
||||||
|
source = Repo.reload(source)
|
||||||
|
|
||||||
|
assert source.nfo_filepath
|
||||||
|
assert source.nfo_filepath == Path.join([source.series_directory, "tvshow.nfo"])
|
||||||
|
assert File.exists?(source.nfo_filepath)
|
||||||
|
|
||||||
|
File.rm!(source.nfo_filepath)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not store the NFO if not specified" do
|
||||||
|
stub(YtDlpRunnerMock, :run, fn
|
||||||
|
_url, _opts, ot when ot == @source_details_ot ->
|
||||||
|
filename = Path.join([Application.get_env(:pinchflat, :media_directory), "Season 1", "bar.mp4"])
|
||||||
|
|
||||||
|
{:ok, source_details_return_fixture(%{filename: filename})}
|
||||||
|
|
||||||
|
_url, _opts, ot when ot == @metadata_ot ->
|
||||||
|
{:ok, "{}"}
|
||||||
|
end)
|
||||||
|
|
||||||
|
source = source_fixture(%{download_nfo: false, nfo_filepath: nil})
|
||||||
|
perform_job(SourceMetadataStorageWorker, %{id: source.id})
|
||||||
|
source = Repo.reload(source)
|
||||||
|
|
||||||
|
refute source.nfo_filepath
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not store the NFO if the series directory cannot be determined" do
|
||||||
|
stub(YtDlpRunnerMock, :run, fn
|
||||||
|
_url, _opts, ot when ot == @source_details_ot ->
|
||||||
|
filename = Path.join([Application.get_env(:pinchflat, :media_directory), "foo", "bar.mp4"])
|
||||||
|
|
||||||
|
{:ok, source_details_return_fixture(%{filename: filename})}
|
||||||
|
|
||||||
|
_url, _opts, ot when ot == @metadata_ot ->
|
||||||
|
{:ok, "{}"}
|
||||||
|
end)
|
||||||
|
|
||||||
|
source = source_fixture(%{download_nfo: true, nfo_filepath: nil})
|
||||||
|
perform_job(SourceMetadataStorageWorker, %{id: source.id})
|
||||||
|
source = Repo.reload(source)
|
||||||
|
|
||||||
|
refute source.nfo_filepath
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ defmodule Pinchflat.SourcesTest do
|
||||||
|
|
||||||
alias Pinchflat.Sources
|
alias Pinchflat.Sources
|
||||||
alias Pinchflat.Sources.Source
|
alias Pinchflat.Sources.Source
|
||||||
|
alias Pinchflat.Filesystem.FilesystemHelpers
|
||||||
alias Pinchflat.Metadata.MetadataFileHelpers
|
alias Pinchflat.Metadata.MetadataFileHelpers
|
||||||
alias Pinchflat.Downloading.DownloadingHelpers
|
alias Pinchflat.Downloading.DownloadingHelpers
|
||||||
alias Pinchflat.FastIndexing.FastIndexingWorker
|
alias Pinchflat.FastIndexing.FastIndexingWorker
|
||||||
|
|
@ -474,9 +475,19 @@ defmodule Pinchflat.SourcesTest do
|
||||||
|
|
||||||
{:ok, updated_source} = Sources.update_source(source, update_attrs)
|
{:ok, updated_source} = Sources.update_source(source, update_attrs)
|
||||||
|
|
||||||
assert {:ok, _} = Sources.delete_source(updated_source, delete_files: true)
|
assert {:ok, _} = Sources.delete_source(updated_source)
|
||||||
refute File.exists?(updated_source.metadata.metadata_filepath)
|
refute File.exists?(updated_source.metadata.metadata_filepath)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "does not delete the source's non-metadata files" do
|
||||||
|
filepath = FilesystemHelpers.generate_metadata_tmpfile(:nfo)
|
||||||
|
source = source_fixture(%{nfo_filepath: filepath})
|
||||||
|
|
||||||
|
assert {:ok, _} = Sources.delete_source(source)
|
||||||
|
assert File.exists?(filepath)
|
||||||
|
|
||||||
|
File.rm!(filepath)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "delete_source/2 when deleting files" do
|
describe "delete_source/2 when deleting files" do
|
||||||
|
|
@ -498,6 +509,15 @@ defmodule Pinchflat.SourcesTest do
|
||||||
|
|
||||||
refute File.exists?(media_item.media_filepath)
|
refute File.exists?(media_item.media_filepath)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "deletes the source's non-metadata files" do
|
||||||
|
filepath = FilesystemHelpers.generate_metadata_tmpfile(:nfo)
|
||||||
|
source = source_fixture(%{nfo_filepath: filepath})
|
||||||
|
|
||||||
|
assert {:ok, _} = Sources.delete_source(source, delete_files: true)
|
||||||
|
|
||||||
|
refute File.exists?(filepath)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "change_source/3" do
|
describe "change_source/3" do
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue