diff --git a/Jellyfin.Plugin.Themerr.Tests/FixtureJellyfinServer.cs b/Jellyfin.Plugin.Themerr.Tests/FixtureJellyfinServer.cs
index 2a76c94..25ab6a6 100644
--- a/Jellyfin.Plugin.Themerr.Tests/FixtureJellyfinServer.cs
+++ b/Jellyfin.Plugin.Themerr.Tests/FixtureJellyfinServer.cs
@@ -1,22 +1,25 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider;
-using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
namespace Jellyfin.Plugin.Themerr.Tests;
///
-/// This class is used as a fixture for the Jellyfin server with mock movies
+/// This class is used as a fixture for the Jellyfin server with mock media items (movies and series).
///
public class FixtureJellyfinServer
{
///
- /// Mock movies to use for testing
+ /// Mock media items to use for testing
///
- /// List containing mock objects.
- public static List MockMovies()
+ /// List containing mock objects.
+ public static List MockItems()
{
- return new List
+ return new List
{
- new()
+ new Movie
{
Name = "Elephants Dream",
ProductionYear = 2006,
@@ -26,7 +29,7 @@ public static List MockMovies()
{ MetadataProvider.Tmdb.ToString(), "9761"},
}
},
- new()
+ new Movie
{
Name = "Sita Sings the Blues",
ProductionYear = 2008,
@@ -36,7 +39,7 @@ public static List MockMovies()
{ MetadataProvider.Tmdb.ToString(), "20529"},
}
},
- new()
+ new Movie
{
Name = "Big Buck Bunny",
ProductionYear = 2008,
@@ -46,7 +49,7 @@ public static List MockMovies()
{ MetadataProvider.Tmdb.ToString(), "10378"},
}
},
- new()
+ new Movie
{
Name = "Sintel",
ProductionYear = 2010,
@@ -56,18 +59,38 @@ public static List MockMovies()
{ MetadataProvider.Tmdb.ToString(), "45745"},
}
},
+ new Series
+ {
+ Name = "Game of Thrones",
+ ProductionYear = 2011,
+ ProviderIds = new Dictionary
+ {
+ { MetadataProvider.Imdb.ToString(), "tt0944947"},
+ { MetadataProvider.Tmdb.ToString(), "1399"},
+ }
+ },
+ new Series
+ {
+ Name = "The 100",
+ ProductionYear = 2014,
+ ProviderIds = new Dictionary
+ {
+ { MetadataProvider.Imdb.ToString(), "tt2661044"},
+ { MetadataProvider.Tmdb.ToString(), "48866"},
+ }
+ }
};
}
///
- /// Mock movies without an associated theme in ThemerrDB to use for testing
+ /// Mock items without an associated theme in ThemerrDB to use for testing
///
- /// List containing mock objects.
- public static List MockMovies2()
+ /// List containing mock objects.
+ public static List MockItems2()
{
- return new List
+ return new List
{
- new()
+ new Movie
{
Name = "Themerr Test Movie",
ProductionYear = 1970,
@@ -77,17 +100,46 @@ public static List MockMovies2()
{ MetadataProvider.Tmdb.ToString(), "0"},
}
},
+ new Series
+ {
+ Name = "Themerr Test Show",
+ ProductionYear = 1970,
+ ProviderIds = new Dictionary
+ {
+ { MetadataProvider.Imdb.ToString(), "tt0000000"},
+ { MetadataProvider.Tmdb.ToString(), "0"},
+ }
+ },
};
}
///
- /// Create mock movies from stub video
+ /// Mock items which are not supported by Themerr
+ ///
+ /// List containing mock objects.
+ public static List UnsupportedMockItems()
+ {
+ return new List
+ {
+ new MusicAlbum
+ {
+ Name = "Themerr Test Album",
+ },
+ new MusicArtist
+ {
+ Name = "Themerr Test Artist",
+ },
+ };
+ }
+
+ ///
+ /// Create mock items from stub video
///
[Fact]
[Trait("Category", "Init")]
- private void CreateMockMovies()
+ private void CreateMockItems()
{
- var mockMovies = MockMovies();
+ var mockItems = MockItems();
// get the stub video path based on the directory of this file
var stubVideoPath = Path.Combine(
@@ -97,29 +149,50 @@ private void CreateMockMovies()
Assert.True(File.Exists(stubVideoPath), "Could not find ./data/video_stub.mp4");
- foreach (var movie in mockMovies)
+ foreach (var item in mockItems)
{
// copy the ./data/video_stub.mp4 to the movie folder "movie.Name (movie.ProductionYear)"
- var movieFolder = Path.Combine(
+ var itemFolder = Path.Combine(
"themerr_jellyfin_tests",
- $"{movie.Name} ({movie.ProductionYear})");
+ $"{item.Name} ({item.ProductionYear})");
// create the movie folder
- Directory.CreateDirectory(movieFolder);
+ Directory.CreateDirectory(itemFolder);
- // copy the video_stub.mp4 to the movie folder, renaming it based on movie name
- var movieVideoPath = Path.Combine(
- movieFolder,
- $"{movie.Name} ({movie.ProductionYear}).mp4");
+ string? itemVideoPath = null;
+ if (item is Movie)
+ {
+ // copy the video_stub.mp4 to the movie folder, renaming it based on movie name
+ itemVideoPath = Path.Combine(
+ itemFolder,
+ $"{item.Name} ({item.ProductionYear}).mp4");
+ }
+ else if (item is Series)
+ {
+ // season folder
+ var seasonFolder = Path.Combine(
+ itemFolder,
+ "Season 01");
+ Directory.CreateDirectory(seasonFolder);
+
+ // copy the video_stub.mp4 to the season folder, renaming it based on series name
+ itemVideoPath = Path.Combine(
+ seasonFolder,
+ $"{item.Name} ({item.ProductionYear}) - S01E01 - Episode Name.mp4");
+ }
+ else
+ {
+ Assert.Fail($"Unknown item type: {item.GetType()}");
+ }
// if file does not exist
- if (!File.Exists(movieVideoPath))
+ if (!File.Exists(itemVideoPath))
{
- // copy the stub video to the movie folder
- File.Copy(stubVideoPath, movieVideoPath);
+ // copy the stub video to the item folder
+ File.Copy(stubVideoPath, itemVideoPath);
}
- Assert.True(File.Exists(movieVideoPath), $"Could not find {movieVideoPath}");
+ Assert.True(File.Exists(itemVideoPath), $"Could not find {itemVideoPath}");
}
}
}
diff --git a/Jellyfin.Plugin.Themerr.Tests/TestThemerrManager.cs b/Jellyfin.Plugin.Themerr.Tests/TestThemerrManager.cs
index 7dd351d..9285f31 100644
--- a/Jellyfin.Plugin.Themerr.Tests/TestThemerrManager.cs
+++ b/Jellyfin.Plugin.Themerr.Tests/TestThemerrManager.cs
@@ -1,3 +1,5 @@
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using Microsoft.Extensions.Logging;
using Moq;
@@ -183,52 +185,70 @@ private void TestSaveMp3InvalidUrl()
[Fact]
[Trait("Category", "Unit")]
- private void TestGetMoviesFromLibrary()
+ private void TestGetTmdbItemsFromLibrary()
{
- var movies = _themerrManager.GetMoviesFromLibrary();
+ var items = _themerrManager.GetTmdbItemsFromLibrary();
- // movies list should be empty
- Assert.Empty(movies);
+ // items list should be empty
+ Assert.Empty(items);
- // todo: test with actual movies
+ // todo: test with actual items
}
- // todo: fix this test
- // [Fact]
- // [Trait("Category", "Unit")]
- // private void TestProcessMovieTheme()
- // {
- // // get fixture movies
- // var mockMovies = FixtureJellyfinServer.MockMovies();
- //
- // Assert.True(mockMovies.Count > 0, "mockMovies.Count is not greater than 0");
- //
- // foreach (var movie in mockMovies)
- // {
- // // get the movie theme
- // _themerrManager.ProcessMovieTheme(movie);
- //
- // Assert.True(File.Exists(_themerrManager.GetThemePath(movie)), $"File {_themerrManager.GetThemePath(movie)} does not exist");
- // }
- // }
+ [Fact]
+ [Trait("Category", "Unit")]
+ private void TestProcessItemTheme()
+ {
+ // get fixture movies
+ var mockItems = FixtureJellyfinServer.MockItems();
+
+ Assert.True(mockItems.Count > 0, "mockItems.Count is not greater than 0");
+
+ foreach (var item in mockItems)
+ {
+ // get the item theme
+ _themerrManager.ProcessItemTheme(item);
+
+ Assert.True(File.Exists(_themerrManager.GetThemePath(item)), $"File {_themerrManager.GetThemePath(item)} does not exist");
+
+ // cleanup and delete the file
+ File.Delete(_themerrManager.GetThemePath(item));
+ }
+ }
+
+ [Fact]
+ [Trait("Category", "Unit")]
+ private void TestProcessItemThemeUnsupportedType()
+ {
+ // get fixture items
+ var mockItems = FixtureJellyfinServer.UnsupportedMockItems();
+
+ foreach (var item in mockItems)
+ {
+ // get the item theme
+ _themerrManager.ProcessItemTheme(item);
+
+ Assert.False(File.Exists(_themerrManager.GetThemePath(item)), $"File {_themerrManager.GetThemePath(item)} exists");
+ }
+ }
[Fact]
[Trait("Category", "Unit")]
private void TestGetTmdbId()
{
- // get fixture movies
- var mockMovies = FixtureJellyfinServer.MockMovies();
+ // get fixture items
+ var mockItems = FixtureJellyfinServer.MockItems();
- foreach (var movie in mockMovies)
+ foreach (var item in mockItems)
{
- // get the movie theme
- var tmdbId = _themerrManager.GetTmdbId(movie);
+ // get the item theme
+ var tmdbId = _themerrManager.GetTmdbId(item);
// ensure tmdbId is not empty
Assert.NotEmpty(tmdbId);
- // ensure tmdbId is the same as the one in the movie fixture
- Assert.Equal(movie.ProviderIds[MetadataProvider.Tmdb.ToString()], tmdbId);
+ // ensure tmdbId is the same as the one in the item fixture
+ Assert.Equal(item.ProviderIds[MetadataProvider.Tmdb.ToString()], tmdbId);
}
}
@@ -237,19 +257,19 @@ private void TestGetTmdbId()
// [Trait("Category", "Unit")]
// private void TestGetThemeProvider()
// {
- // // get fixture movies
- // var mockMovies = FixtureJellyfinServer.MockMovies();
+ // // get fixture items
+ // var mockItems = FixtureJellyfinServer.MockItems();
//
- // foreach (var movie in mockMovies)
+ // foreach (var item in mockItems)
// {
- // // get the movie theme
- // var themeProvider = _themerrManager.GetThemeProvider(movie);
+ // // get the item theme
+ // var themeProvider = _themerrManager.GetThemeProvider(item);
//
// // ensure themeProvider null
// Assert.Null(themeProvider);
// }
//
- // // todo: test with actual movies
+ // // todo: test with actual items
// }
[Fact]
@@ -334,53 +354,104 @@ private void TestContinueDownload()
[Trait("Category", "Unit")]
private void TestGetThemePath()
{
- // get fixture movies
- var mockMovies = FixtureJellyfinServer.MockMovies();
+ // get fixture items
+ var mockItems = FixtureJellyfinServer.MockItems();
- Assert.True(mockMovies.Count > 0, "mockMovies.Count is not greater than 0");
+ Assert.True(mockItems.Count > 0, "mockItems.Count is not greater than 0");
- foreach (var movie in mockMovies)
+ foreach (var item in mockItems)
{
- // get the movie theme
- var themePath = _themerrManager.GetThemePath(movie);
+ // get the item theme
+ var themePath = _themerrManager.GetThemePath(item);
// ensure path ends with theme.mp3
Assert.EndsWith("theme.mp3", themePath);
}
}
+ [Fact]
+ [Trait("Category", "Unit")]
+ private void TestGetThemePathUnsupportedType()
+ {
+ // get fixture items
+ var mockItems = FixtureJellyfinServer.UnsupportedMockItems();
+
+ Assert.True(mockItems.Count > 0, "mockItems.Count is not greater than 0");
+
+ foreach (var item in mockItems)
+ {
+ // get the item theme
+ var themePath = _themerrManager.GetThemePath(item);
+
+ // ensure path is null
+ Assert.Null(themePath);
+ }
+ }
+
[Fact]
[Trait("Category", "Unit")]
private void TestGetThemerrDataPath()
{
- // get fixture movies
- var mockMovies = FixtureJellyfinServer.MockMovies();
+ // get fixture items
+ var mockItems = FixtureJellyfinServer.MockItems();
- Assert.True(mockMovies.Count > 0, "mockMovies.Count is not greater than 0");
+ Assert.True(mockItems.Count > 0, "mockItems.Count is not greater than 0");
- foreach (var movie in mockMovies)
+ foreach (var item in mockItems)
{
- // get the movie theme
- var themerrDataPath = _themerrManager.GetThemerrDataPath(movie);
+ // get the item theme
+ var themerrDataPath = _themerrManager.GetThemerrDataPath(item);
// ensure path ends with theme.mp3
Assert.EndsWith("themerr.json", themerrDataPath);
}
}
+ [Fact]
+ [Trait("Category", "Unit")]
+ private void TestGetThemerrDataPathUnsupportedType()
+ {
+ // get fixture items
+ var mockItems = FixtureJellyfinServer.UnsupportedMockItems();
+
+ Assert.True(mockItems.Count > 0, "mockItems.Count is not greater than 0");
+
+ foreach (var item in mockItems)
+ {
+ // get the item theme
+ var themerrDataPath = _themerrManager.GetThemerrDataPath(item);
+
+ // ensure path is null
+ Assert.Null(themerrDataPath);
+ }
+ }
+
[Fact]
[Trait("Category", "Unit")]
private void TestCreateThemerrDbLink()
{
- // get fixture movies
- var mockMovies = FixtureJellyfinServer.MockMovies();
+ // get fixture items
+ var mockItems = FixtureJellyfinServer.MockItems();
- Assert.True(mockMovies.Count > 0, "mockMovies.Count is not greater than 0");
+ Assert.True(mockItems.Count > 0, "mockItems.Count is not greater than 0");
- foreach (var movie in mockMovies)
+ foreach (var item in mockItems)
{
- var tmdbId = movie.ProviderIds[MetadataProvider.Tmdb.ToString()];
- var themerrDbUrl = _themerrManager.CreateThemerrDbLink(tmdbId);
+ var dbType = item switch
+ {
+ Movie _ => "movies",
+ Series _ => "tv_shows",
+ _ => null
+ };
+
+ // return if dbType is null
+ if (string.IsNullOrEmpty(dbType))
+ {
+ Assert.Fail($"Unknown item type: {item.GetType()}");
+ }
+
+ var tmdbId = item.ProviderIds[MetadataProvider.Tmdb.ToString()];
+ var themerrDbUrl = _themerrManager.CreateThemerrDbLink(tmdbId, dbType);
TestLogger.Info($"themerrDbLink: {themerrDbUrl}");
@@ -392,20 +463,33 @@ private void TestCreateThemerrDbLink()
[Trait("Category", "Unit")]
private void TestGetYoutubeThemeUrl()
{
- // get fixture movies
- var mockMovies = FixtureJellyfinServer.MockMovies();
+ // get fixture items
+ var mockItems = FixtureJellyfinServer.MockItems();
- Assert.True(mockMovies.Count > 0, "mockMovies.Count is not greater than 0");
+ Assert.True(mockItems.Count > 0, "mockItems.Count is not greater than 0");
- // loop over each movie
- foreach (var movie in mockMovies)
+ // loop over each item
+ foreach (var item in mockItems)
{
+ var dbType = item switch
+ {
+ Movie _ => "movies",
+ Series _ => "tv_shows",
+ _ => null
+ };
+
+ // return if dbType is null
+ if (string.IsNullOrEmpty(dbType))
+ {
+ Assert.Fail($"Unknown item type: {item.GetType()}");
+ }
+
// get themerrDbUrl
- var tmdbId = _themerrManager.GetTmdbId(movie);
- var themerrDbLink = _themerrManager.CreateThemerrDbLink(tmdbId);
+ var tmdbId = _themerrManager.GetTmdbId(item);
+ var themerrDbLink = _themerrManager.CreateThemerrDbLink(tmdbId, dbType);
// get the new youtube theme url
- var youtubeThemeUrl = _themerrManager.GetYoutubeThemeUrl(themerrDbLink, movie);
+ var youtubeThemeUrl = _themerrManager.GetYoutubeThemeUrl(themerrDbLink, item);
// log
TestLogger.Info($"youtubeThemeUrl: {youtubeThemeUrl}");
@@ -418,20 +502,33 @@ private void TestGetYoutubeThemeUrl()
[Trait("Category", "Unit")]
private void TestGetYoutubeThemeUrlExceptions()
{
- // get fixture movies
- var mockMovies = FixtureJellyfinServer.MockMovies2();
+ // get fixture items
+ var mockItems = FixtureJellyfinServer.MockItems2();
- Assert.True(mockMovies.Count > 0, "mockMovies.Count is not greater than 0");
+ Assert.True(mockItems.Count > 0, "mockItems.Count is not greater than 0");
- // loop over each movie
- foreach (var movie in mockMovies)
+ // loop over each item
+ foreach (var item in mockItems)
{
+ var dbType = item switch
+ {
+ Movie _ => "movies",
+ Series _ => "tv_shows",
+ _ => null
+ };
+
+ // return if dbType is null
+ if (string.IsNullOrEmpty(dbType))
+ {
+ Assert.Fail($"Unknown item type: {item.GetType()}");
+ }
+
// get themerrDbUrl
- var tmdbId = _themerrManager.GetTmdbId(movie);
- var themerrDbLink = _themerrManager.CreateThemerrDbLink(tmdbId);
+ var tmdbId = _themerrManager.GetTmdbId(item);
+ var themerrDbLink = _themerrManager.CreateThemerrDbLink(tmdbId, dbType);
// get the new youtube theme url
- var youtubeThemeUrl = _themerrManager.GetYoutubeThemeUrl(themerrDbLink, movie);
+ var youtubeThemeUrl = _themerrManager.GetYoutubeThemeUrl(themerrDbLink, item);
Assert.Empty(youtubeThemeUrl);
}
@@ -441,22 +538,42 @@ private void TestGetYoutubeThemeUrlExceptions()
[Trait("Category", "Unit")]
private void TestGetIssueUrl()
{
- // get fixture movies
- var mockMovies = FixtureJellyfinServer.MockMovies();
+ // get fixture items
+ var mockItems = FixtureJellyfinServer.MockItems();
- Assert.True(mockMovies.Count > 0, "mockMovies.Count is not greater than 0");
+ Assert.True(mockItems.Count > 0, "mockItems.Count is not greater than 0");
- // loop over each movie
- foreach (var movie in mockMovies)
+ // loop over each item
+ foreach (var item in mockItems)
{
+ var issueType = item switch
+ {
+ Movie _ => "MOVIE",
+ Series _ => "TV SHOW",
+ _ => null
+ };
+
+ var tmdbEndpoint = item switch
+ {
+ Movie _ => "movie",
+ Series _ => "tv",
+ _ => null
+ };
+
+ // return if dbType is null
+ if (string.IsNullOrEmpty(issueType) || string.IsNullOrEmpty(tmdbEndpoint))
+ {
+ Assert.Fail($"Unknown item type: {item.GetType()}");
+ }
+
// parts of expected url
- var tmdbId = _themerrManager.GetTmdbId(movie);
- var encodedName = movie.Name.Replace(" ", "%20");
- var year = movie.ProductionYear;
- var expectedUrl = $"https://github.com/LizardByte/ThemerrDB/issues/new?assignees=&labels=request-theme&template=theme.yml&title=[MOVIE]:%20{encodedName}%20({year})&database_url=https://www.themoviedb.org/movie/{tmdbId}";
+ var tmdbId = _themerrManager.GetTmdbId(item);
+ var encodedName = item.Name.Replace(" ", "%20");
+ var year = item.ProductionYear;
+ var expectedUrl = $"https://github.com/LizardByte/ThemerrDB/issues/new?assignees=&labels=request-theme&template=theme.yml&title=[{issueType}]:%20{encodedName}%20({year})&database_url=https://www.themoviedb.org/{tmdbEndpoint}/{tmdbId}";
// get the new youtube theme url
- var issueUrl = _themerrManager.GetIssueUrl(movie);
+ var issueUrl = _themerrManager.GetIssueUrl(item);
Assert.NotEmpty(issueUrl);
@@ -468,6 +585,25 @@ private void TestGetIssueUrl()
}
}
+ [Fact]
+ [Trait("Category", "Unit")]
+ private void TestGetIssueUrlUnsupportedType()
+ {
+ // get fixture items
+ var mockItems = FixtureJellyfinServer.UnsupportedMockItems();
+
+ Assert.True(mockItems.Count > 0, "mockItems.Count is not greater than 0");
+
+ // loop over each item
+ foreach (var item in mockItems)
+ {
+ // get the new youtube theme url
+ var issueUrl = _themerrManager.GetIssueUrl(item);
+
+ Assert.Null(issueUrl);
+ }
+ }
+
[Fact]
[Trait("Category", "Unit")]
private void TestSaveThemerrData()
diff --git a/Jellyfin.Plugin.Themerr/Api/ThemerrController.cs b/Jellyfin.Plugin.Themerr/Api/ThemerrController.cs
index 64eace8..a7b21b9 100644
--- a/Jellyfin.Plugin.Themerr/Api/ThemerrController.cs
+++ b/Jellyfin.Plugin.Themerr/Api/ThemerrController.cs
@@ -3,7 +3,6 @@
using System.Linq;
using System.Net.Mime;
using System.Threading.Tasks;
-using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -49,7 +48,7 @@ public ThemerrController(
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task TriggerUpdateRequest()
{
- _logger.LogInformation("Updating Movie Theme Songs");
+ _logger.LogInformation("Updating Theme Songs");
await _themerrManager.UpdateAll();
_logger.LogInformation("Completed");
}
@@ -57,11 +56,11 @@ public async Task TriggerUpdateRequest()
///
/// Get the data required to populate the progress dashboard.
///
- /// Loop over all Jellyfin libraries and movies, creating a json object with the following structure:
+ /// Loop over all Jellyfin libraries and supported items, creating a json object with the following structure:
/// {
- /// "items": [Movies],
- /// "media_count": Movies.Count,
- /// "media_percent_complete": ThemedMovies.Count / Movies.Count * 100,
+ /// "items": [BaseItems],
+ /// "media_count": BaseItems.Count,
+ /// "media_percent_complete": ThemedItems.Count / BaseItems.Count * 100,
/// }
///
/// JSON object containing progress data.
@@ -75,29 +74,30 @@ public ActionResult GetProgress()
var mediaWithThemes = 0;
var mediaPercentComplete = 0;
- var movies = _themerrManager.GetMoviesFromLibrary();
+ var items = _themerrManager.GetTmdbItemsFromLibrary();
- // sort movies by name, then year
- var enumerable = movies.OrderBy(m => m.Name).ThenBy(m => m.ProductionYear);
+ // sort items by name, then year
+ var enumerable = items.OrderBy(i => i.Name).ThenBy(i => i.ProductionYear);
- foreach (var movie in enumerable)
+ foreach (var item in enumerable)
{
- var year = movie.ProductionYear;
- var issueUrl = _themerrManager.GetIssueUrl(movie);
- var themeProvider = _themerrManager.GetThemeProvider(movie);
- var item = new
+ var year = item.ProductionYear;
+ var issueUrl = _themerrManager.GetIssueUrl(item);
+ var themeProvider = _themerrManager.GetThemeProvider(item);
+ var tmpItem = new
{
- name = movie.Name,
- id = movie.Id,
+ name = item.Name,
+ id = item.Id,
issue_url = issueUrl,
theme_provider = themeProvider,
+ type = item.GetType().Name, // Movie, Series, etc.
year = year
};
- tmpItems.Add(item);
+ tmpItems.Add(tmpItem);
mediaCount++;
- var themeSongs = movie.GetThemeSongs();
+ var themeSongs = item.GetThemeSongs();
if (themeSongs.Count > 0)
{
mediaWithThemes++;
diff --git a/Jellyfin.Plugin.Themerr/Configuration/configPage.html b/Jellyfin.Plugin.Themerr/Configuration/configPage.html
index 3ca35a0..8907203 100644
--- a/Jellyfin.Plugin.Themerr/Configuration/configPage.html
+++ b/Jellyfin.Plugin.Themerr/Configuration/configPage.html
@@ -64,7 +64,7 @@ Themerr
-
+
@@ -204,7 +204,7 @@ Themerr
let tableHeaderRow = document.createElement('tr')
tableHeader.appendChild(tableHeaderRow)
- let columns = ['Title', 'Year', 'Contribute', 'Status']
+ let columns = ['Title', 'Year', 'Type', 'Contribute', 'Status']
// create table header columns
for (let column in columns) {
@@ -216,10 +216,12 @@ Themerr
// loop over items
for (let item in response["items"]) {
+ console.log(LogPrefix + "------------------")
console.log(LogPrefix + "item: " + item)
console.log(LogPrefix + "name: " + response["items"][item]["name"])
console.log(LogPrefix + "id: " + response["items"][item]["id"])
console.log(LogPrefix + "issue_url: " + response["items"][item]["issue_url"])
+ console.log(LogPrefix + "type: " + response["items"][item]["type"])
console.log(LogPrefix + "theme_provider: " + response["items"][item]["theme_provider"])
console.log(LogPrefix + "year: " + response["items"][item]["year"])
@@ -235,6 +237,10 @@ Themerr
let tableColumnYear = document.createElement('td')
tableColumnYear.innerHTML = response["items"][item]["year"]
tableRow.appendChild(tableColumnYear)
+
+ let tableColumnType = document.createElement('td')
+ tableColumnType.innerHTML = response["items"][item]["type"]
+ tableRow.appendChild(tableColumnType)
let contributeButton = document.createElement('a')
contributeButton.setAttribute('is', 'emby-linkbutton')
diff --git a/Jellyfin.Plugin.Themerr/ScheduledTasks/ThemerrTasks.cs b/Jellyfin.Plugin.Themerr/ScheduledTasks/ThemerrTasks.cs
index c51946d..2f267e7 100644
--- a/Jellyfin.Plugin.Themerr/ScheduledTasks/ThemerrTasks.cs
+++ b/Jellyfin.Plugin.Themerr/ScheduledTasks/ThemerrTasks.cs
@@ -40,7 +40,7 @@ public ThemerrTasks(ILibraryManager libraryManager, ILogger logg
///
/// Gets the description of the task.
///
- public string Description => "Scans all libraries to download Movie Theme Songs";
+ public string Description => "Scans all libraries to download supported Theme Songs";
///
/// Gets the category of the task.
@@ -55,7 +55,7 @@ public ThemerrTasks(ILibraryManager libraryManager, ILogger logg
/// A representing the asynchronous operation.
public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken)
{
- _logger.LogInformation("Starting plugin, Downloading Movie Theme Songs...");
+ _logger.LogInformation("Starting plugin, Downloading supported Theme Songs...");
await _themerrManager.UpdateAll();
_logger.LogInformation("All theme songs downloaded");
}
diff --git a/Jellyfin.Plugin.Themerr/ThemerrManager.cs b/Jellyfin.Plugin.Themerr/ThemerrManager.cs
index 681dbe6..3d8d6bc 100644
--- a/Jellyfin.Plugin.Themerr/ThemerrManager.cs
+++ b/Jellyfin.Plugin.Themerr/ThemerrManager.cs
@@ -7,6 +7,7 @@
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Entities;
@@ -15,9 +16,6 @@
using YoutubeExplode;
using YoutubeExplode.Videos.Streams;
-// TODO: Add support for TV shows
-// using MediaBrowser.Controller.Entities.TV;
-
namespace Jellyfin.Plugin.Themerr
{
///
@@ -94,59 +92,71 @@ public bool SaveMp3(string destination, string videoUrl)
}
///
- /// Get all movies from the library that have a tmdb id.
+ /// Get all supported items from the library that have a tmdb id.
///
- /// List of .
- public IEnumerable GetMoviesFromLibrary()
+ /// List of objects.
+ public IEnumerable GetTmdbItemsFromLibrary()
{
- var movies = _libraryManager.GetItemList(new InternalItemsQuery
+ var items = _libraryManager.GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new[] {BaseItemKind.Movie},
+ IncludeItemTypes = new[]
+ {
+ BaseItemKind.Movie,
+ BaseItemKind.Series
+ },
IsVirtualItem = false,
Recursive = true,
HasTmdbId = true
});
- var movieList = new List();
- if (movies == null || movies.Count == 0)
+ var itemList = new List();
+ if (items == null || items.Count == 0)
{
- return movieList;
+ return itemList;
}
- foreach (var movie in movies)
- {
- if (movie is Movie m)
- {
- movieList.Add(m);
- }
- }
+ itemList.AddRange(items.Where(item => item is Movie or Series));
- return movieList;
+ return itemList;
}
///
- /// Enumerate through all movies in the library and downloads their theme songs as required.
+ /// Enumerate through all supported items in the library and downloads their theme songs as required.
///
/// A representing the asynchronous operation.
public Task UpdateAll()
{
- var movies = GetMoviesFromLibrary();
- foreach (var movie in movies)
+ var items = GetTmdbItemsFromLibrary();
+ foreach (var item in items)
{
- ProcessMovieTheme(movie);
+ ProcessItemTheme(item);
}
return Task.CompletedTask;
}
///
- /// Download the theme song for a movie if it doesn't already exist.
+ /// Download the theme song for a media item if it doesn't already exist.
///
- /// The Jellyfin movie object.
- public void ProcessMovieTheme(Movie movie)
+ /// The Jellyfin media object.
+ public void ProcessItemTheme(BaseItem item)
{
- var themePath = GetThemePath(movie);
- var themerrDataPath = GetThemerrDataPath(movie);
+ // get themerrDB database type, used to create the themerrdb url
+ var dbType = item switch
+ {
+ Movie _ => "movies",
+ Series _ => "tv_shows",
+ _ => null
+ };
+
+ // return if dbType is null
+ if (string.IsNullOrEmpty(dbType))
+ {
+ return;
+ }
+
+ var themePath = GetThemePath(item);
+ var themerrDataPath = GetThemerrDataPath(item);
if (!ContinueDownload(themePath, themerrDataPath))
{
@@ -156,12 +166,12 @@ public void ProcessMovieTheme(Movie movie)
var existingYoutubeThemeUrl = GetExistingThemerrDataValue("youtube_theme_url", themerrDataPath);
// get tmdb id
- var tmdbId = GetTmdbId(movie);
+ var tmdbId = GetTmdbId(item);
// create themerrdb url
- var themerrDbUrl = CreateThemerrDbLink(tmdbId);
+ var themerrDbUrl = CreateThemerrDbLink(tmdbId, dbType);
- var youtubeThemeUrl = GetYoutubeThemeUrl(themerrDbUrl, movie);
+ var youtubeThemeUrl = GetYoutubeThemeUrl(themerrDbUrl, item);
// skip if no youtube theme url in ThemerrDB or
// if the youtube themes match AND the theme_md5 is unknown
@@ -184,35 +194,35 @@ public void ProcessMovieTheme(Movie movie)
return;
}
- movie.RefreshMetadata(CancellationToken.None);
+ item.RefreshMetadata(CancellationToken.None);
}
///
- /// Get TMDB id from a movie.
+ /// Get TMDB id from an item.
///
- /// The Jellyfin movie object.
+ /// The Jellyfin media object.
/// TMDB id.
- public string GetTmdbId(Movie movie)
+ public string GetTmdbId(BaseItem item)
{
- var tmdbId = movie.GetProviderId(MetadataProvider.Tmdb);
+ var tmdbId = item.GetProviderId(MetadataProvider.Tmdb);
return tmdbId;
}
///
/// Get the theme provider.
///
- /// The Jellyfin movie object.
+ /// The Jellyfin media object.
/// The theme provider.
- public string GetThemeProvider(Movie movie)
+ public string GetThemeProvider(BaseItem item)
{
// check if item has a theme song
- var themeSongs = movie.GetThemeSongs();
+ var themeSongs = item.GetThemeSongs();
if (themeSongs == null || themeSongs.Count == 0)
{
return null;
}
- var themerrDataPath = GetThemerrDataPath(movie);
+ var themerrDataPath = GetThemerrDataPath(item);
var themerrHash = GetExistingThemerrDataValue("theme_md5", themerrDataPath);
var themeHash = GetMd5Hash(themeSongs[0].Path);
@@ -267,40 +277,51 @@ public bool ContinueDownload(string themePath, string themerrDataPath)
///
/// Get the path to the theme song.
///
- /// The Jellyfin movie object.
+ /// The Jellyfin media object.
/// The path to the theme song.
- public string GetThemePath(Movie movie)
+ public string GetThemePath(BaseItem item)
{
- return $"{movie.ContainingFolderPath}/theme.mp3";
+ return item switch
+ {
+ Movie movie => System.IO.Path.Join(movie.ContainingFolderPath, "theme.mp3"),
+ Series series => System.IO.Path.Join(series.Path, "theme.mp3"),
+ _ => null
+ };
}
///
/// Get the path to the themerr data file.
///
- /// The Jellyfin movie object.
+ /// The Jellyfin media object.
/// The path to the themerr data file.
- public string GetThemerrDataPath(Movie movie)
+ public string GetThemerrDataPath(BaseItem item)
{
- return $"{movie.ContainingFolderPath}/themerr.json";
+ return item switch
+ {
+ Movie movie => System.IO.Path.Join(movie.ContainingFolderPath, "themerr.json"),
+ Series series => System.IO.Path.Join(series.Path, "themerr.json"),
+ _ => null
+ };
}
///
/// Create a link to the themerr database.
///
/// The tmdb id.
+ /// The database type.
/// The themerr database link.
- public string CreateThemerrDbLink(string tmdbId)
+ public string CreateThemerrDbLink(string tmdbId, string dbType)
{
- return $"https://app.lizardbyte.dev/ThemerrDB/movies/themoviedb/{tmdbId}.json";
+ return $"https://app.lizardbyte.dev/ThemerrDB/{dbType}/themoviedb/{tmdbId}.json";
}
///
/// Get the YouTube theme url from the themerr database.
///
/// The themerr database url.
- /// The Jellyfin movie object.
+ /// The Jellyfin media object.
/// The YouTube theme url.
- public string GetYoutubeThemeUrl(string themerrDbUrl, Movie movie)
+ public string GetYoutubeThemeUrl(string themerrDbUrl, BaseItem item)
{
var client = new HttpClient();
@@ -313,9 +334,9 @@ public string GetYoutubeThemeUrl(string themerrDbUrl, Movie movie)
catch (Exception)
{
_logger.LogWarning(
- "Missing from ThemerrDB: {MovieTitle}, contribute:\n {IssueUrl}",
- movie.Name,
- GetIssueUrl(movie));
+ "Missing from ThemerrDB: {ItemTitle}, contribute:\n {IssueUrl}",
+ item.Name,
+ GetIssueUrl(item));
return string.Empty;
}
}
@@ -325,18 +346,37 @@ public string GetYoutubeThemeUrl(string themerrDbUrl, Movie movie)
///
/// This url can be used to easily add/edit theme songs in ThemerrDB.
///
- /// The Jellyfin movie object.
+ /// The Jellyfin media object.
/// The ThemerrDB issue url.
- public string GetIssueUrl(Movie movie)
+ public string GetIssueUrl(BaseItem item)
{
+ string issueBaseTitle = item switch
+ {
+ Movie _ => "MOVIE",
+ Series _ => "TV SHOW",
+ _ => null
+ };
+
+ string tmdbEndpoint = item switch
+ {
+ Movie _ => "movie",
+ Series _ => "tv",
+ _ => null
+ };
+
+ // return if either is null
+ if (string.IsNullOrEmpty(issueBaseTitle) || string.IsNullOrEmpty(tmdbEndpoint))
+ {
+ return null;
+ }
+
// url components
- const string issueBase =
- "https://github.com/LizardByte/ThemerrDB/issues/new?assignees=&labels=request-theme&template=theme.yml&title=[MOVIE]:%20";
- const string databaseBase = "https://www.themoviedb.org/movie/";
+ string issueBase = $"https://github.com/LizardByte/ThemerrDB/issues/new?assignees=&labels=request-theme&template=theme.yml&title=[{issueBaseTitle}]:%20";
+ string databaseBase = $"https://www.themoviedb.org/{tmdbEndpoint}/";
- var urlEncodedName = movie.Name.Replace(" ", "%20");
- var year = movie.ProductionYear;
- var tmdbId = GetTmdbId(movie);
+ var urlEncodedName = item.Name.Replace(" ", "%20");
+ var year = item.ProductionYear;
+ var tmdbId = GetTmdbId(item);
var issueUrl = $"{issueBase}{urlEncodedName}%20({year})&database_url={databaseBase}{tmdbId}";
return issueUrl;
diff --git a/docs/Doxyfile b/docs/Doxyfile
index 6d1d8c4..d8b0a4e 100644
--- a/docs/Doxyfile
+++ b/docs/Doxyfile
@@ -54,7 +54,7 @@ PROJECT_NUMBER =
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.
-PROJECT_BRIEF = "Plugin for Jellyfin that adds theme songs to movies using ThemerrDB."
+PROJECT_BRIEF = "Plugin for Jellyfin that adds theme songs to movies and tv shows using ThemerrDB."
# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55
diff --git a/docs/source/about/usage.rst b/docs/source/about/usage.rst
index db587fd..8758746 100644
--- a/docs/source/about/usage.rst
+++ b/docs/source/about/usage.rst
@@ -11,10 +11,12 @@ Enable Themes
#. Select `Display` from the user section.
#. Within the `Library` section, ensure `Theme songs` is enabled.
-Movie Directory Structure
--------------------------
+Directory Structure
+-------------------
-.. Attention:: Jellyfin requires movies to be stored in separate subdirectories, with each movie in its own folder.
+.. Attention:: Jellyfin requires your media to be stored in separate subdirectories, with each movie/show in its
+ own folder. See `Movies `__
+ or `TV Shows `__ for more information.
Task Activation
---------------
diff --git a/docs/source/conf.py b/docs/source/conf.py
index ef7a53f..df97880 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -118,9 +118,15 @@
'Jellyfin.Controller.MediaBrowser.Model.Tasks': (
'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Model/Tasks/%s.cs',
),
+ 'Jellyfin.Controller.MediaBrowser.Controller.Entities': (
+ 'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Controller/Entities/%s.cs',
+ ),
'Jellyfin.Controller.MediaBrowser.Controller.Entities.Movies': (
'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Controller/Entities/Movies/%s.cs',
),
+ 'Jellyfin.Controller.MediaBrowser.Controller.Entities.TV': (
+ 'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Controller/Entities/TV/%s.cs',
+ ),
'Jellyfin.Controller.MediaBrowser.Controller.Entities.Library': (
'https://github.com/jellyfin/jellyfin/blob/v10.8.13/MediaBrowser.Controller/Library/%s.cs',
),
@@ -209,15 +215,25 @@
'TaskTriggerInfo',
],
},
+ 'Jellyfin.Controller.MediaBrowser.Controller.Entities': {
+ '': [
+ 'BaseItem',
+ ],
+ },
+ 'Jellyfin.Controller.MediaBrowser.Controller.Entities.Library': {
+ '': [
+ 'ILibraryManager',
+ ],
+ },
'Jellyfin.Controller.MediaBrowser.Controller.Entities.Movies': {
'': [
'BoxSet',
'Movie',
],
},
- 'Jellyfin.Controller.MediaBrowser.Controller.Entities.Library': {
+ 'Jellyfin.Controller.MediaBrowser.Controller.Entities.TV': {
'': [
- 'ILibraryManager',
+ 'Series',
],
},
'Jellyfin.Controller.MediaBrowser.Controller.Plugins': {
diff --git a/docs/source/contributing/testing.rst b/docs/source/contributing/testing.rst
index be00be6..6ba52ad 100644
--- a/docs/source/contributing/testing.rst
+++ b/docs/source/contributing/testing.rst
@@ -21,9 +21,6 @@ Themerr-jellyfin uses `Sphinx `__ for doc
other required python dependencies are included in the `./docs/requirements.txt` file. Python is required to build
sphinx docs. Installation and setup of python will not be covered here.
-.. todo::
- Add documentation within C# code to be included in sphinx docs.
-
The config file for Sphinx is ``docs/source/conf.py``. This is already included in the root of the repo and should not
be modified.