using System.Globalization; using System.Text.RegularExpressions; using Bugsnag; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Metadata; namespace ErsatzTV.Core.Metadata; public partial class FallbackMetadataProvider : IFallbackMetadataProvider { private static readonly Regex SeasonPattern = SeasonNumber(); private readonly IClient _client; public FallbackMetadataProvider(IClient client) => _client = client; public Option GetSeasonNumberForFolder(string folder) { string folderName = Path.GetFileName(folder) ?? folder; if (int.TryParse(folderName, out int seasonNumber)) { return seasonNumber; } Match match = SeasonPattern.Match(folderName); if (match.Success && int.TryParse(match.Groups[1].Value, out seasonNumber)) { return seasonNumber; } if (int.TryParse(folder.Split(" ").Last(), out seasonNumber)) { return seasonNumber; } if (folder.EndsWith("specials", StringComparison.OrdinalIgnoreCase)) { return 0; } return None; } public ShowMetadata GetFallbackMetadataForShow(string showFolder) { string fileName = Path.GetFileName(showFolder); var metadata = new ShowMetadata { MetadataKind = MetadataKind.Fallback, Title = fileName ?? showFolder, Genres = new List(), Tags = new List(), Studios = new List(), Actors = new List() }; return GetTelevisionShowMetadata(fileName, metadata); } public ArtistMetadata GetFallbackMetadataForArtist(string artistFolder) { string fileName = Path.GetFileName(artistFolder); return new ArtistMetadata { MetadataKind = MetadataKind.Fallback, Title = fileName ?? artistFolder }; } public List GetFallbackMetadata(Episode episode) { string path = episode.MediaVersions.Head().MediaFiles.Head().Path; string fileName = Path.GetFileName(path); var baseMetadata = new EpisodeMetadata { MetadataKind = MetadataKind.Fallback, Title = Path.GetFileNameWithoutExtension(path) ?? path, DateAdded = DateTime.UtcNow, EpisodeNumber = 0, Actors = new List(), Artwork = new List(), Directors = new List(), Genres = new List(), Guids = new List(), Studios = new List(), Tags = new List(), Writers = new List() }; return fileName != null ? GetEpisodeMetadata(fileName, baseMetadata) : new List { baseMetadata }; } public MovieMetadata GetFallbackMetadata(Movie movie) { string path = movie.MediaVersions.Head().MediaFiles.Head().Path; string fileName = Path.GetFileName(path); var metadata = new MovieMetadata { MetadataKind = MetadataKind.Fallback, Title = Path.GetFileNameWithoutExtension(path) ?? path, Genres = new List(), Tags = new List(), Studios = new List(), Actors = new List(), Directors = new List(), Writers = new List(), Guids = new List() }; return fileName != null ? GetMovieMetadata(fileName, metadata) : metadata; } public Option GetFallbackMetadata(MusicVideo musicVideo) { string path = musicVideo.MediaVersions.Head().MediaFiles.Head().Path; string fileName = Path.GetFileName(path); var metadata = new MusicVideoMetadata { MetadataKind = MetadataKind.Fallback, Title = fileName ?? path }; return GetMusicVideoMetadata(fileName, metadata); } public Option GetFallbackMetadata(OtherVideo otherVideo) { string path = otherVideo.MediaVersions.Head().MediaFiles.Head().Path; string fileName = Path.GetFileNameWithoutExtension(path); var metadata = new OtherVideoMetadata { MetadataKind = MetadataKind.Fallback, Title = fileName ?? path, OtherVideo = otherVideo, Genres = new List(), Tags = new List(), Studios = new List(), Actors = new List(), Directors = new List(), Writers = new List(), Guids = new List() }; return GetOtherVideoMetadata(path, metadata); } public Option GetFallbackMetadata(Song song) { string path = song.MediaVersions.Head().MediaFiles.Head().Path; string fileName = Path.GetFileNameWithoutExtension(path); var metadata = new SongMetadata { MetadataKind = MetadataKind.Fallback, Title = fileName ?? path, Song = song }; return GetSongMetadata(path, metadata); } public Option GetFallbackMetadata(Image image) { string path = image.MediaVersions.Head().MediaFiles.Head().Path; string fileName = Path.GetFileNameWithoutExtension(path); var metadata = new ImageMetadata { MetadataKind = MetadataKind.Fallback, Title = fileName ?? path, Image = image, Genres = [], Tags = [], Studios = [], Actors = [], Guids = [] }; return GetImageMetadata(path, metadata); } [GeneratedRegex(@"s(?:eason)?\s?(\d+)(?![e\d])", RegexOptions.IgnoreCase)] private static partial Regex SeasonNumber(); private List GetEpisodeMetadata(string fileName, EpisodeMetadata baseMetadata) { var result = new List { baseMetadata }; try { const string PATTERN = @"[sS]\d+[\._xX]?[eE]([e\-\d{1,2}]+)"; const string PATTERN_2 = @"\d+[\._xX]([e\-\d{1,2}]+)"; MatchCollection matches = Regex.Matches(fileName, PATTERN); if (matches.Count == 0) { matches = Regex.Matches(fileName, PATTERN_2); } if (matches.Count > 0) { var episodeNumbers = matches.Bind( m => m.Groups[1].Value .Replace('e', '-') .Split('-') .Bind(ep => int.TryParse(ep, out int num) ? Some(num) : Option.None)) .ToList(); switch (episodeNumbers.Count) { case 0: break; case 1: baseMetadata.EpisodeNumber = episodeNumbers.Head(); break; default: result.Clear(); foreach (int episodeNumber in episodeNumbers) { var metadata = new EpisodeMetadata { MetadataKind = MetadataKind.Fallback, EpisodeNumber = episodeNumber, DateAdded = baseMetadata.DateAdded, DateUpdated = baseMetadata.DateAdded, Title = baseMetadata.Title, Actors = new List(), Artwork = new List(), Directors = new List(), Genres = new List(), Guids = new List(), Studios = new List(), Tags = new List(), Writers = new List() }; result.Add(metadata); } break; } } } catch (Exception ex) { _client.Notify(ex); } return result; } private MovieMetadata GetMovieMetadata(string fileName, MovieMetadata metadata) { try { const string PATTERN = @"^(.*?)[.\(](\d{4})[.\)].*\.\w+$"; Match match = Regex.Match(fileName, PATTERN); if (match.Success) { metadata.Title = match.Groups[1].Value.Trim(); metadata.Year = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture); metadata.ReleaseDate = new DateTime(int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture), 1, 1); metadata.DateUpdated = DateTime.UtcNow; } } catch (Exception ex) { _client.Notify(ex); } return metadata; } private Option GetMusicVideoMetadata(string fileName, MusicVideoMetadata metadata) { try { const string PATTERN = @"^(.*?) - (.*?).\w+$"; Match match = Regex.Match(fileName, PATTERN); metadata.Title = match.Success ? match.Groups[2].Value.Trim() : Path.GetFileNameWithoutExtension(fileName); metadata.Artists = []; metadata.Genres = []; metadata.Tags = []; metadata.Studios = []; metadata.Directors = []; metadata.DateUpdated = DateTime.UtcNow; return metadata; } catch (Exception ex) { _client.Notify(ex); return None; } } private Option GetOtherVideoMetadata(string path, OtherVideoMetadata metadata) { try { string folder = Path.GetDirectoryName(path); if (folder == null) { return None; } string libraryPath = metadata.OtherVideo.LibraryPath.Path; string parent = Optional(Directory.GetParent(libraryPath)).Match( di => di.FullName, () => libraryPath); string diff = Path.GetRelativePath(parent, folder); var tags = diff.Split(Path.DirectorySeparatorChar) .Map(t => new Tag { Name = t }) .ToList(); metadata.Artwork = new List(); metadata.Actors = new List(); metadata.Genres = new List(); metadata.Tags = tags; metadata.Studios = new List(); metadata.DateUpdated = DateTime.UtcNow; metadata.OriginalTitle = Path.GetRelativePath(libraryPath, path); return metadata; } catch (Exception ex) { _client.Notify(ex); return None; } } private Option GetImageMetadata(string path, ImageMetadata metadata) { try { string folder = Path.GetDirectoryName(path); if (folder == null) { return None; } string libraryPath = metadata.Image.LibraryPath.Path; string parent = Optional(Directory.GetParent(libraryPath)).Match( di => di.FullName, () => libraryPath); string diff = Path.GetRelativePath(parent, folder); var tags = diff.Split(Path.DirectorySeparatorChar) .Map(t => new Tag { Name = t }) .ToList(); metadata.Artwork = []; metadata.Actors = []; metadata.Genres = []; metadata.Tags = tags; metadata.Studios = []; metadata.DateUpdated = DateTime.UtcNow; metadata.OriginalTitle = Path.GetRelativePath(libraryPath, path); return metadata; } catch (Exception ex) { _client.Notify(ex); return None; } } private Option GetSongMetadata(string path, SongMetadata metadata) { try { string folder = Path.GetDirectoryName(path); if (folder == null) { return None; } string libraryPath = metadata.Song.LibraryPath.Path; string parent = Optional(Directory.GetParent(libraryPath)).Match( di => di.FullName, () => libraryPath); string diff = Path.GetRelativePath(parent, folder); var tags = diff.Split(Path.DirectorySeparatorChar) .Map(t => new Tag { Name = t }) .ToList(); metadata.Artwork = new List(); metadata.Actors = new List(); metadata.Genres = new List(); metadata.Tags = tags; metadata.Studios = new List(); metadata.DateUpdated = DateTime.UtcNow; metadata.OriginalTitle = Path.GetRelativePath(libraryPath, path); return metadata; } catch (Exception ex) { _client.Notify(ex); return None; } } private ShowMetadata GetTelevisionShowMetadata(string fileName, ShowMetadata metadata) { try { const string PATTERN = @"^(.*?)[\s.]*?[.\(](\d{4})[.\)].*$"; Match match = Regex.Match(fileName, PATTERN); if (match.Success) { metadata.Title = match.Groups[1].Value; metadata.Year = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture); metadata.ReleaseDate = new DateTime(int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture), 1, 1); metadata.DateUpdated = DateTime.UtcNow; } } catch (Exception ex) { _client.Notify(ex); } return metadata; } }