diff --git a/plugins/jellyfin/Jellyfin.Plugin.MovieNight/Configuration/ui.js b/plugins/jellyfin/Jellyfin.Plugin.MovieNight/Configuration/ui.js index 3f48df3..4786f13 100644 --- a/plugins/jellyfin/Jellyfin.Plugin.MovieNight/Configuration/ui.js +++ b/plugins/jellyfin/Jellyfin.Plugin.MovieNight/Configuration/ui.js @@ -185,12 +185,22 @@ const footer = dialog.querySelector('.dialog-footer'); content.innerHTML = ` -
- +
+
+
+
+ + +
+
+ + +
+
- +
`; @@ -206,10 +216,12 @@ btnAdd.onclick = async () => { const title = dialog.querySelector('.txtTitle').value; + const year = dialog.querySelector('.txtYear').value; + const imdbId = dialog.querySelector('.txtImdb').value; const url = dialog.querySelector('.txtUrl').value; if (!title) return; cleanup(); - await addMovie(title, url); + await addMovie(title, url, year, imdbId); }; dialog.querySelector('.btnCancel').onclick = cleanup; @@ -241,12 +253,16 @@ } } - async function addMovie(title, url) { + async function addMovie(title, url, year, imdbId) { try { + const data = { title, url }; + if (year) data.year = parseInt(year); + if (imdbId) data.imdbId = imdbId; + await ApiClient.ajax({ type: 'POST', url: ApiClient.getUrl(`MovieNight/Films`), - data: JSON.stringify({ title, url }), + data: JSON.stringify(data), contentType: 'application/json' }); showMsg(`STRM file created for "${title}". Refresh your library to see it.`); diff --git a/plugins/jellyfin/Jellyfin.Plugin.MovieNight/Controllers/MovieNightController.cs b/plugins/jellyfin/Jellyfin.Plugin.MovieNight/Controllers/MovieNightController.cs index 7cc1781..58b51ac 100644 --- a/plugins/jellyfin/Jellyfin.Plugin.MovieNight/Controllers/MovieNightController.cs +++ b/plugins/jellyfin/Jellyfin.Plugin.MovieNight/Controllers/MovieNightController.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Jellyfin.Plugin.MovieNight.Services; @@ -136,7 +138,8 @@ public class MovieNightController : ControllerBase } /// - /// Creates a new film by generating a .strm file. + /// Creates a new film by generating a .strm file in a folder-per-movie structure. + /// Structure: Movie Name (Year) [imdbid-ttXXXXXXX]/Movie Name (Year) [imdbid-ttXXXXXXX].strm /// [HttpPost("Films")] [Authorize] @@ -148,24 +151,45 @@ public class MovieNightController : ControllerBase return BadRequest("STRM output path is not configured."); } + if (string.IsNullOrWhiteSpace(request.Title)) + { + return BadRequest("Movie title is required."); + } + try { - if (!Directory.Exists(config.StrmOutputPath)) + // Construct name: "Movie Name (Year) [imdbid-ttXXXXXXX]" + var folderName = request.Title.Trim(); + if (request.Year.HasValue) { - Directory.CreateDirectory(config.StrmOutputPath); + folderName += $" ({request.Year})"; + } + if (!string.IsNullOrWhiteSpace(request.ImdbId)) + { + var ttId = request.ImdbId.Trim().ToLowerInvariant(); + if (!ttId.StartsWith("tt")) ttId = "tt" + ttId; + folderName += $" [imdbid-{ttId}]"; } - var safeTitle = string.Join("_", request.Title.Split(Path.GetInvalidFileNameChars())); - var fileName = $"{safeTitle}.strm"; - var filePath = Path.Combine(config.StrmOutputPath, fileName); + // Sanitize for file system + var invalidChars = Path.GetInvalidFileNameChars(); + var safeFolderName = new string(folderName.Select(c => invalidChars.Contains(c) ? '_' : c).ToArray()); + + var movieDirectory = Path.Combine(config.StrmOutputPath, safeFolderName); + if (!Directory.Exists(movieDirectory)) + { + Directory.CreateDirectory(movieDirectory); + } + + var filePath = Path.Combine(movieDirectory, $"{safeFolderName}.strm"); var strmContent = string.IsNullOrWhiteSpace(request.Url) ? "http://placeholder.url/upload_me_later" - : request.Url; + : request.Url.Trim(); await System.IO.File.WriteAllTextAsync(filePath, strmContent).ConfigureAwait(false); - return Ok(new { FilePath = filePath }); + return Ok(new { FilePath = filePath, FolderName = safeFolderName }); } catch (Exception ex) { @@ -177,7 +201,7 @@ public class MovieNightController : ControllerBase /// /// Create film request. /// -public sealed record CreateFilmRequest(string Title, string? Url); +public sealed record CreateFilmRequest(string Title, string? Url, int? Year, string? ImdbId); /// /// Rating request.