Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bugfix] Determine NFO season and episode from filepath #320

Merged
merged 2 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions lib/pinchflat/metadata/metadata_file_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,21 @@ defmodule Pinchflat.Metadata.MetadataFileHelpers do
end
end

@doc """
Attempts to determine the season and episode number from a media filepath.

Returns {:ok, {binary(), binary()}} | {:error, :indeterminable}
"""
def season_and_episode_from_media_filepath(media_filepath) do
# matches s + 1 or more digits + e + 1 or more digits (case-insensitive)
season_episode_regex = ~r/s(\d+)e(\d+)/i

case Regex.scan(season_episode_regex, media_filepath) do
[[_, season, episode] | _] -> {:ok, {season, episode}}
_ -> {:error, :indeterminable}
end
end

defp generate_filepath_for(database_record, filename) do
Path.join([
metadata_directory_for(database_record),
Expand Down
30 changes: 21 additions & 9 deletions lib/pinchflat/metadata/nfo_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ defmodule Pinchflat.Metadata.NfoBuilder do

import Pinchflat.Utils.XmlUtils, only: [safe: 1]

alias Pinchflat.Metadata.MetadataFileHelpers
alias Pinchflat.Utils.FilesystemUtils
alias Pinchflat.Metadata.MetadataFileHelpers

@doc """
Builds an NFO file for a media item (read: single "episode") and
stores it at the specified location.

Returns the filepath of the NFO file.
"""
def build_and_store_for_media_item(filepath, metadata) do
nfo = build_for_media_item(metadata)
def build_and_store_for_media_item(nfo_filepath, metadata) do
nfo = build_for_media_item(nfo_filepath, metadata)

FilesystemUtils.write_p!(filepath, nfo)
FilesystemUtils.write_p!(nfo_filepath, nfo)

filepath
nfo_filepath
end

@doc """
Expand All @@ -37,10 +37,15 @@ defmodule Pinchflat.Metadata.NfoBuilder do
filepath
end

defp build_for_media_item(metadata) do
defp build_for_media_item(nfo_filepath, metadata) do
upload_date = MetadataFileHelpers.parse_upload_date(metadata["upload_date"])
# NOTE: the filepath here isn't the path of the media item, it's the path that
# the NFO should be saved to. This works because the NFO's path is the same as
# the media's path, just with a different extension. If this ever changes I'll
# need to pass in the media item's path as well.
{season, episode} = determine_season_and_episode_number(nfo_filepath, 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>
Expand All @@ -49,8 +54,8 @@ defmodule Pinchflat.Metadata.NfoBuilder do
<uniqueid type="youtube" default="true">#{safe(metadata["id"])}</uniqueid>
<plot>#{safe(metadata["description"])}</plot>
<aired>#{safe(upload_date)}</aired>
<season>#{safe(upload_date.year)}</season>
<episode>#{Calendar.strftime(upload_date, "%m%d")}</episode>
<season>#{safe(season)}</season>
<episode>#{episode}</episode>
<genre>YouTube</genre>
</episodedetails>
"""
Expand All @@ -67,4 +72,11 @@ defmodule Pinchflat.Metadata.NfoBuilder do
</tvshow>
"""
end

defp determine_season_and_episode_number(filepath, upload_date) do
case MetadataFileHelpers.season_and_episode_from_media_filepath(filepath) do
{:ok, {season, episode}} -> {season, episode}
{:error, _} -> {upload_date.year, Calendar.strftime(upload_date, "%m%d")}
end
end
end
29 changes: 23 additions & 6 deletions test/pinchflat/metadata/metadata_file_helpers_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ defmodule Pinchflat.Metadata.MetadataFileHelpersTest do
{:ok, %{media_item: media_item}}
end

describe "metadata_directory_for/1" do
test "returns the metadata directory for the given record", %{media_item: media_item} do
base_metadata_directory = Application.get_env(:pinchflat, :metadata_directory)

metadata_directory = Helpers.metadata_directory_for(media_item)

assert metadata_directory == Path.join([base_metadata_directory, "media_items", "#{media_item.id}"])
end
end

describe "compress_and_store_metadata_for/2" do
test "returns the filepath", %{media_item: media_item} do
metadata_map = %{"foo" => "bar"}
Expand Down Expand Up @@ -142,13 +152,20 @@ defmodule Pinchflat.Metadata.MetadataFileHelpersTest do
end
end

describe "metadata_directory_for/1" do
test "returns the metadata directory for the given record", %{media_item: media_item} do
base_metadata_directory = Application.get_env(:pinchflat, :metadata_directory)

metadata_directory = Helpers.metadata_directory_for(media_item)
describe "season_and_episode_from_media_filepath/1" do
test "returns a season and episode if one can be determined" do
assert {:ok, {"1", "2"}} = Helpers.season_and_episode_from_media_filepath("/foo/s1e2 - test.mp4")
assert {:ok, {"1", "2"}} = Helpers.season_and_episode_from_media_filepath("/foo/S1E2 - test.mp4")
assert {:ok, {"001", "002"}} = Helpers.season_and_episode_from_media_filepath("/foo/s001e002 - test.mp4")
assert {:ok, {"1", "2"}} = Helpers.season_and_episode_from_media_filepath("/foo/s1e2bar - test.mp4")
assert {:ok, {"1", "2"}} = Helpers.season_and_episode_from_media_filepath("/foo/bar s1e2 - test.mp4")
end

assert metadata_directory == Path.join([base_metadata_directory, "media_items", "#{media_item.id}"])
test "returns an error if a season and episode can't be determined" do
assert {:error, :indeterminable} = Helpers.season_and_episode_from_media_filepath("/foo/test.mp4")
assert {:error, :indeterminable} = Helpers.season_and_episode_from_media_filepath("/foo/s1 - test.mp4")
assert {:error, :indeterminable} = Helpers.season_and_episode_from_media_filepath("/foo/s1e - test.mp4")
assert {:error, :indeterminable} = Helpers.season_and_episode_from_media_filepath("/foo/s1etest.mp4")
end
end
end
39 changes: 38 additions & 1 deletion test/pinchflat/metadata/nfo_builder_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Pinchflat.Metadata.NfoBuilderTest do
alias Pinchflat.Utils.FilesystemUtils

setup do
filepath = FilesystemUtils.generate_metadata_tmpfile(:json)
filepath = FilesystemUtils.generate_metadata_tmpfile(:nfo)

on_exit(fn -> File.rm!(filepath) end)

Expand Down Expand Up @@ -45,6 +45,43 @@ defmodule Pinchflat.Metadata.NfoBuilderTest do

assert String.contains?(nfo, "hello&#39; &amp; &lt;world&gt;")
end

test "uses the season and episode number from the filepath if it can be determined" do
metadata = %{
"title" => "title",
"uploader" => "uploader",
"id" => "id",
"description" => "description",
"upload_date" => "20210101"
}

tmpfile_directory = Application.get_env(:pinchflat, :tmpfile_directory)
filepath = Path.join([tmpfile_directory, "foo/s0123e456.nfo"])

result = NfoBuilder.build_and_store_for_media_item(filepath, metadata)
nfo = File.read!(result)

assert String.contains?(nfo, "<season>0123</season>")
assert String.contains?(nfo, "<episode>456</episode>")

File.rm!(filepath)
end

test "uses the upload date if the season and episode number can't be determined", %{filepath: filepath} do
metadata = %{
"title" => "title",
"uploader" => "uploader",
"id" => "id",
"description" => "description",
"upload_date" => "20210101"
}

result = NfoBuilder.build_and_store_for_media_item(filepath, metadata)
nfo = File.read!(result)

assert String.contains?(nfo, "<season>2021</season>")
assert String.contains?(nfo, "<episode>0101</episode>")
end
end

describe "build_and_store_for_source/2" do
Expand Down
Loading