public async Task <bool> TryExtractRelationshipsAsync(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > aspects, IList <IDictionary <Guid, IList <MediaItemAspect> > > extractedLinkedAspects) { SeriesInfo reimport = null; if (aspects.ContainsKey(ReimportAspect.ASPECT_ID)) { EpisodeInfo episode = new EpisodeInfo(); episode.FromMetadata(aspects); reimport = episode.CloneBasicInstance <SeriesInfo>(); } IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData = extractedLinkedAspects.Count > 0 ? extractedLinkedAspects[0] : new Dictionary <Guid, IList <MediaItemAspect> >(); if (!await TryExtractSeriesMetadataAsync(mediaItemAccessor, extractedAspectData, reimport).ConfigureAwait(false)) { return(false); } SeriesInfo seriesInfo = new SeriesInfo(); if (!seriesInfo.FromMetadata(extractedAspectData)) { return(false); } IGenreConverter converter = ServiceRegistration.Get <IGenreConverter>(); foreach (var genre in seriesInfo.Genres) { if (!genre.Id.HasValue && converter.GetGenreId(genre.Name, GenreCategory.Series, null, out int genreId)) { genre.Id = genreId; } } seriesInfo.SetMetadata(extractedAspectData); if (!extractedAspectData.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { return(false); } bool episodeVirtual; if (MediaItemAspect.TryGetAttribute(aspects, MediaAspect.ATTR_ISVIRTUAL, false, out episodeVirtual)) { MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_ISVIRTUAL, episodeVirtual); } extractedLinkedAspects.Clear(); extractedLinkedAspects.Add(extractedAspectData); return(true); }
public bool TryExtractRelationships(IDictionary <Guid, IList <MediaItemAspect> > aspects, bool importOnly, out IList <RelationshipItem> extractedLinkedAspects) { extractedLinkedAspects = null; //Only run during import if (!importOnly) { return(false); } EpisodeInfo episodeInfo = new EpisodeInfo(); if (!episodeInfo.FromMetadata(aspects)) { return(false); } SeriesInfo seriesInfo = episodeInfo.CloneBasicInstance <SeriesInfo>(); UpdatePersons(aspects, seriesInfo.Actors, true); UpdateCharacters(aspects, seriesInfo.Characters, true); if (!UpdateSeries(aspects, seriesInfo)) { return(false); } OnlineMatcherService.Instance.AssignMissingSeriesGenreIds(seriesInfo.Genres); extractedLinkedAspects = new List <RelationshipItem>(); IDictionary <Guid, IList <MediaItemAspect> > seriesAspects = new Dictionary <Guid, IList <MediaItemAspect> >(); seriesInfo.SetMetadata(seriesAspects); if (aspects.ContainsKey(EpisodeAspect.ASPECT_ID)) { bool episodeVirtual = true; if (MediaItemAspect.TryGetAttribute(aspects, MediaAspect.ATTR_ISVIRTUAL, false, out episodeVirtual)) { MediaItemAspect.SetAttribute(seriesAspects, MediaAspect.ATTR_ISVIRTUAL, episodeVirtual); } } if (!seriesAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { return(false); } StorePersons(seriesAspects, seriesInfo.Actors, true); StoreCharacters(seriesAspects, seriesInfo.Characters, true); extractedLinkedAspects.Add(new RelationshipItem(seriesAspects, Guid.Empty)); return(true); }
public async Task <bool> TryExtractRelationshipsAsync(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > aspects, IList <IDictionary <Guid, IList <MediaItemAspect> > > extractedLinkedAspects) { EpisodeInfo episodeInfo = new EpisodeInfo(); if (!episodeInfo.FromMetadata(aspects)) { return(false); } EpisodeInfo reimport = null; if (aspects.ContainsKey(ReimportAspect.ASPECT_ID)) { reimport = episodeInfo; } int?season = episodeInfo.SeasonNumber; int?episode = episodeInfo.EpisodeNumbers != null && episodeInfo.EpisodeNumbers.Any() ? episodeInfo.EpisodeNumbers.First() : (int?)null; IList <IDictionary <Guid, IList <MediaItemAspect> > > nfoLinkedAspects = new List <IDictionary <Guid, IList <MediaItemAspect> > >(); if (!await TryExtractEpisodeCharactersMetadataAsync(mediaItemAccessor, nfoLinkedAspects, season, episode, reimport).ConfigureAwait(false)) { return(false); } List <CharacterInfo> characters; if (!RelationshipExtractorUtils.TryCreateInfoFromLinkedAspects(nfoLinkedAspects, out characters)) { return(false); } characters = characters.Where(c => c != null && !string.IsNullOrEmpty(c.Name)).ToList(); if (characters.Count == 0) { return(false); } extractedLinkedAspects.Clear(); foreach (CharacterInfo character in characters) { if (character.SetLinkedMetadata() && character.LinkedAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { extractedLinkedAspects.Add(character.LinkedAspects); } } return(extractedLinkedAspects.Count > 0); }
public bool TryExtractRelationships(IDictionary <Guid, IList <MediaItemAspect> > aspects, bool importOnly, out IList <RelationshipItem> extractedLinkedAspects) { extractedLinkedAspects = null; if (!NfoSeriesMetadataExtractor.IncludeActorDetails) { return(false); } //Only run during import if (!importOnly) { return(false); } EpisodeInfo episodeInfo = new EpisodeInfo(); if (!episodeInfo.FromMetadata(aspects)) { return(false); } if (!UpdatePersons(aspects, episodeInfo.Actors, false)) { return(false); } if (episodeInfo.Actors.Count == 0) { return(false); } extractedLinkedAspects = new List <RelationshipItem>(); foreach (PersonInfo person in episodeInfo.Actors) { person.AssignNameId(); person.HasChanged = true; IDictionary <Guid, IList <MediaItemAspect> > personAspects = new Dictionary <Guid, IList <MediaItemAspect> >(); person.SetMetadata(personAspects); if (personAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { extractedLinkedAspects.Add(new RelationshipItem(personAspects, Guid.Empty)); } } return(extractedLinkedAspects.Count > 0); }
/// <summary> /// Constructor. /// </summary> /// <param name="filename">Filename of the currently played episode</param> public SeriesEpisodeInfo(MediaItem mediaItem) { try { SeriesId = Guid.Empty; SeasonId = Guid.Empty; if (mediaItem.Aspects.ContainsKey(EpisodeAspect.ASPECT_ID)) { if (MediaItemAspect.TryGetAspects(mediaItem.Aspects, RelationshipAspect.Metadata, out IList <MultipleMediaItemAspect> relationAspects)) { foreach (MultipleMediaItemAspect relation in relationAspects) { if ((Guid?)relation[RelationshipAspect.ATTR_LINKED_ROLE] == SeriesAspect.ROLE_SERIES) { SeriesId = (Guid)relation[RelationshipAspect.ATTR_LINKED_ID]; } if ((Guid?)relation[RelationshipAspect.ATTR_LINKED_ROLE] == SeasonAspect.ROLE_SEASON) { SeasonId = (Guid)relation[RelationshipAspect.ATTR_LINKED_ID]; } } } } EpisodeInfo episode = new EpisodeInfo(); episode.FromMetadata(mediaItem.Aspects); Episode = episode.EpisodeNumbers.First(); Season = episode.SeasonNumber.Value; Plot = episode.Summary.Text; Title = episode.EpisodeName.Text; Director = String.Join(", ", episode.Directors); Writer = String.Join(", ", episode.Writers); Genre = String.Join(", ", episode.Genres.Select(g => g.Name)); AirDate = episode.FirstAired.HasValue ? episode.FirstAired.Value.ToLongDateString() : ""; Rating = Convert.ToString(episode.Rating.RatingValue ?? 0); RatingCount = Convert.ToString(episode.Rating.VoteCount ?? 0); Series = episode.SeriesName.Text; ImageName = Helper.GetImageBaseURL(mediaItem, FanArtMediaTypes.Episode, FanArtTypes.Thumbnail); } catch (Exception e) { ServiceRegistration.Get <ILogger>().Error("WifiRemote: Error getting episode info", e); } }
/// <summary> /// Verifies if the episode being reimported matches the episode in the nfo file /// </summary> /// <param name="reader">Reader used read the episode information from the nfo file</param> /// <param name="reimport">The episode being reimported</param> /// <returns>Result of the verification</returns> protected bool VerifyEpisodeReimport(NfoSeriesEpisodeReader reader, EpisodeInfo reimport) { if (reimport == null) { return(true); } IDictionary <Guid, IList <MediaItemAspect> > aspectData = new Dictionary <Guid, IList <MediaItemAspect> >(); if (reader.TryWriteMetadata(aspectData)) { EpisodeInfo info = new EpisodeInfo(); info.FromMetadata(aspectData); if (reimport.Equals(info)) { return(true); } } return(false); }
public async Task <bool> TryExtractRelationshipsAsync(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > aspects, IList <IDictionary <Guid, IList <MediaItemAspect> > > extractedLinkedAspects) { if (BaseInfo.IsVirtualResource(aspects)) { return(false); } EpisodeInfo episodeInfo = new EpisodeInfo(); if (!episodeInfo.FromMetadata(aspects)) { return(false); } if (RelationshipExtractorUtils.TryCreateInfoFromLinkedAspects(extractedLinkedAspects, out List <CharacterInfo> characters)) { episodeInfo.Characters = characters; } if (SeriesMetadataExtractor.IncludeCharacterDetails && !SeriesMetadataExtractor.SkipOnlineSearches) { await OnlineMatcherService.Instance.UpdateEpisodeCharactersAsync(episodeInfo, _category).ConfigureAwait(false); } foreach (CharacterInfo character in episodeInfo.Characters.Take(SeriesMetadataExtractor.MaximumCharacterCount)) { if (character.LinkedAspects != null) { character.SetLinkedMetadata(); } else { IDictionary <Guid, IList <MediaItemAspect> > characterAspects = new Dictionary <Guid, IList <MediaItemAspect> >(); if (character.SetMetadata(characterAspects) && characterAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { extractedLinkedAspects.Add(characterAspects); } } } return(extractedLinkedAspects.Count > 0); }
public async Task <bool> TryExtractRelationshipsAsync(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > aspects, IList <IDictionary <Guid, IList <MediaItemAspect> > > extractedLinkedAspects) { if (BaseInfo.IsVirtualResource(aspects)) { return(false); } EpisodeInfo episodeInfo = new EpisodeInfo(); if (!episodeInfo.FromMetadata(aspects)) { return(false); } if (RelationshipExtractorUtils.TryCreateInfoFromLinkedAspects(extractedLinkedAspects, out List <PersonInfo> directors)) { episodeInfo.Directors = directors; } if (SeriesMetadataExtractor.IncludeDirectorDetails && !SeriesMetadataExtractor.SkipOnlineSearches) { await OnlineMatcherService.Instance.UpdateEpisodePersonsAsync(episodeInfo, PersonAspect.OCCUPATION_DIRECTOR).ConfigureAwait(false); } foreach (PersonInfo person in episodeInfo.Directors) { if (person.LinkedAspects != null) { person.SetLinkedMetadata(); } else { IDictionary <Guid, IList <MediaItemAspect> > personAspects = new Dictionary <Guid, IList <MediaItemAspect> >(); if (person.SetMetadata(personAspects) && personAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { extractedLinkedAspects.Add(personAspects); } } } return(extractedLinkedAspects.Count > 0); }
public async Task <bool> TryExtractRelationshipsAsync(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > aspects, IList <IDictionary <Guid, IList <MediaItemAspect> > > extractedLinkedAspects) { EpisodeInfo episodeInfo = new EpisodeInfo(); if (!episodeInfo.FromMetadata(aspects)) { return(false); } SeasonInfo seasonInfo = episodeInfo.CloneBasicInstance <SeasonInfo>(); if (!SeriesMetadataExtractor.SkipOnlineSearches) { await OnlineMatcherService.Instance.UpdateSeasonAsync(seasonInfo).ConfigureAwait(false); } if (seasonInfo.SeriesName.IsEmpty) { return(false); } IDictionary <Guid, IList <MediaItemAspect> > seasonAspects = new Dictionary <Guid, IList <MediaItemAspect> >(); seasonInfo.SetMetadata(seasonAspects); bool episodeVirtual = true; if (MediaItemAspect.TryGetAttribute(aspects, MediaAspect.ATTR_ISVIRTUAL, false, out episodeVirtual)) { MediaItemAspect.SetAttribute(seasonAspects, MediaAspect.ATTR_ISVIRTUAL, episodeVirtual); } if (!seasonAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { return(false); } extractedLinkedAspects.Add(seasonAspects); return(true); }
public bool TryMatch(IDictionary <Guid, IList <MediaItemAspect> > extractedAspects, IDictionary <Guid, IList <MediaItemAspect> > existingAspects) { if (!existingAspects.ContainsKey(EpisodeAspect.ASPECT_ID)) { return(false); } EpisodeInfo linkedEpisode = new EpisodeInfo(); if (!linkedEpisode.FromMetadata(extractedAspects)) { return(false); } EpisodeInfo existingEpisode = new EpisodeInfo(); if (!existingEpisode.FromMetadata(existingAspects)) { return(false); } return(linkedEpisode.Equals(existingEpisode)); }
public bool TryExtractRelationships(IDictionary <Guid, IList <MediaItemAspect> > aspects, bool importOnly, out IList <RelationshipItem> extractedLinkedAspects) { extractedLinkedAspects = null; if (!SeriesMetadataExtractor.IncludeDirectorDetails) { return(false); } if (importOnly) { return(false); } if (BaseInfo.IsVirtualResource(aspects)) { return(false); } EpisodeInfo episodeInfo = new EpisodeInfo(); if (!episodeInfo.FromMetadata(aspects)) { return(false); } if (CheckCacheContains(episodeInfo)) { return(false); } int count = 0; if (!SeriesMetadataExtractor.SkipOnlineSearches) { OnlineMatcherService.Instance.UpdateEpisodePersons(episodeInfo, PersonAspect.OCCUPATION_DIRECTOR, importOnly); count = episodeInfo.Directors.Where(p => p.HasExternalId).Count(); if (!episodeInfo.IsRefreshed) { episodeInfo.HasChanged = true; //Force save to update external Ids for metadata found by other MDEs } } else { count = episodeInfo.Directors.Where(p => !string.IsNullOrEmpty(p.Name)).Count(); } if (episodeInfo.Directors.Count == 0) { return(false); } if (BaseInfo.CountRelationships(aspects, LinkedRole) < count || (BaseInfo.CountRelationships(aspects, LinkedRole) == 0 && episodeInfo.Directors.Count > 0)) { episodeInfo.HasChanged = true; //Force save if no relationship exists } if (!episodeInfo.HasChanged) { return(false); } AddToCheckCache(episodeInfo); extractedLinkedAspects = new List <RelationshipItem>(); foreach (PersonInfo person in episodeInfo.Directors) { person.AssignNameId(); person.HasChanged = episodeInfo.HasChanged; IDictionary <Guid, IList <MediaItemAspect> > personAspects = new Dictionary <Guid, IList <MediaItemAspect> >(); person.SetMetadata(personAspects); if (personAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { Guid existingId; if (TryGetIdFromCache(person, out existingId)) { extractedLinkedAspects.Add(new RelationshipItem(personAspects, existingId)); } else { extractedLinkedAspects.Add(new RelationshipItem(personAspects, Guid.Empty)); } } } return(extractedLinkedAspects.Count > 0); }
public override async Task CollectFanArtAsync(Guid mediaItemId, IDictionary <Guid, IList <MediaItemAspect> > aspects) { IResourceLocator mediaItemLocator = null; if (!BaseInfo.IsVirtualResource(aspects)) { mediaItemLocator = GetResourceLocator(aspects); } if (!aspects.ContainsKey(EpisodeAspect.ASPECT_ID) || mediaItemLocator == null) { return; } IFanArtCache fanArtCache = ServiceRegistration.Get <IFanArtCache>(); using (IResourceAccessor mediaItemAccessor = mediaItemLocator.CreateAccessor()) { EpisodeInfo episodeInfo = new EpisodeInfo(); if (!episodeInfo.FromMetadata(aspects)) { return; } //Episode fanart if (AddToCache(mediaItemId)) { var existingThumbs = fanArtCache.GetFanArtFiles(mediaItemId, FanArtTypes.Thumbnail); int?season = episodeInfo.SeasonNumber; int?episode = episodeInfo.EpisodeNumbers != null && episodeInfo.EpisodeNumbers.Any() ? episodeInfo.EpisodeNumbers.First() : (int?)null; if (!existingThumbs.Any()) //Only get thumb if needed for better performance { NfoSeriesEpisodeReader episodeReader = await SERIES_EXTRACTOR.TryGetNfoSeriesEpisodeReaderAsync(mediaItemAccessor, season, episode, true).ConfigureAwait(false); if (episodeReader != null) { var stubs = episodeReader.GetEpisodeStubs(); var mainStub = stubs?.FirstOrDefault(); if (mainStub?.Thumb != null) { await fanArtCache.TrySaveFanArt(mediaItemId, episodeInfo.ToString(), FanArtTypes.Thumbnail, p => TrySaveFileImage(mainStub.Thumb, p, "Thumb", "Nfo.")).ConfigureAwait(false); } } } } //Series fanart if (RelationshipExtractorUtils.TryGetLinkedId(SeriesAspect.ROLE_SERIES, aspects, out Guid seriesMediaItemId)) { IList <Tuple <Guid, string> > actors = GetActors(aspects); RelationshipExtractorUtils.TryGetLinkedId(SeasonAspect.ROLE_SEASON, aspects, out Guid seasonMediaItemId); //Check if loading nfo is needed if ((actors?.All(a => IsInCache(a.Item1)) ?? true) && IsInCache(seriesMediaItemId) && (seasonMediaItemId == Guid.Empty || IsInCache(seasonMediaItemId))) { return; //Everything was already saved } NfoSeriesReader seriesNfoReader = await SERIES_EXTRACTOR.TryGetNfoSeriesReaderAsync(mediaItemAccessor, true).ConfigureAwait(false); if (seriesNfoReader != null) { var stubs = seriesNfoReader.GetSeriesStubs(); var mainStub = stubs?.FirstOrDefault(); if (AddToCache(seriesMediaItemId)) { var series = episodeInfo.CloneBasicInstance <SeriesInfo>(); if (mainStub?.Thumbs?.Count > 0) { await TrySaveThumbStubs(fanArtCache, mainStub.Thumbs, null, seriesMediaItemId, series.ToString()); } } if (seasonMediaItemId != Guid.Empty && episodeInfo.SeasonNumber.HasValue && AddToCache(seasonMediaItemId)) { var season = episodeInfo.CloneBasicInstance <SeasonInfo>(); if (mainStub?.Thumbs?.Count > 0) { await TrySaveThumbStubs(fanArtCache, mainStub.Thumbs, episodeInfo.SeasonNumber, seasonMediaItemId, season.ToString()); } } //Actor fanart //We only want the series actors because thumb loading is disabled on episode actors for performance reasons, so we might need to //load the series nfo multiple time before we have all actors depending on what actors are in the episode foreach (var actor in actors) { if (!IsInCache(actor.Item1)) { var existingThumbs = fanArtCache.GetFanArtFiles(actor.Item1, FanArtTypes.Thumbnail); var actorStub = mainStub?.Actors?.FirstOrDefault(a => string.Equals(a?.Name, actor.Item2, StringComparison.InvariantCultureIgnoreCase)); if (actorStub != null || existingThumbs.Any()) //We have a thumb already or no thumb is available, so no need to check again { AddToCache(actor.Item1); } if (actorStub?.Thumb != null) { await fanArtCache.TrySaveFanArt(actor.Item1, actor.Item2, FanArtTypes.Thumbnail, p => TrySaveFileImage(actorStub.Thumb, p, "Thumb", "Nfo.")).ConfigureAwait(false); } } } } } } }
/// <summary> /// Asynchronously tries to extract episode metadata for the given <param name="mediaItemAccessor"></param> /// </summary> /// <param name="mediaItemAccessor">Points to the resource for which we try to extract metadata</param> /// <param name="extractedAspectData">Dictionary of <see cref="MediaItemAspect"/>s with the extracted metadata</param> /// <param name="forceQuickMode">If <c>true</c>, nothing is downloaded from the internet</param> /// <returns><c>true</c> if metadata was found and stored into <param name="extractedAspectData"></param>, else <c>false</c></returns> private async Task <bool> TryExtractEpsiodeMetadataAsync(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData, bool forceQuickMode) { // Get a unique number for this call to TryExtractMetadataAsync. We use this to make reading the debug log easier. // This MetadataExtractor is called in parallel for multiple MediaItems so that the respective debug log entries // for one call are not contained one after another in debug log. We therefore prepend this number before every log entry. var miNumber = Interlocked.Increment(ref _lastMediaItemNumber); bool isStub = extractedAspectData.ContainsKey(StubAspect.ASPECT_ID); try { _debugLogger.Info("[#{0}]: Start extracting metadata for resource '{1}' (forceQuickMode: {2})", miNumber, mediaItemAccessor, forceQuickMode); // This MetadataExtractor only works for MediaItems accessible by an IFileSystemResourceAccessor. // Otherwise it is not possible to find a nfo-file in the MediaItem's directory or parent directory. if (!(mediaItemAccessor is IFileSystemResourceAccessor)) { _debugLogger.Info("[#{0}]: Cannot extract metadata; mediaItemAccessor is not an IFileSystemResourceAccessor", miNumber); return(false); } // We only extract metadata with this MetadataExtractor, if another MetadataExtractor that was applied before // has identified this MediaItem as a video and therefore added a VideoAspect. if (!extractedAspectData.ContainsKey(VideoAspect.ASPECT_ID)) { _debugLogger.Info("[#{0}]: Cannot extract metadata; this resource is not a video", miNumber); return(false); } // Here we try to find an IFileSystemResourceAccessor pointing to the episode nfo-file. // If we don't find one, we cannot extract any metadata. IFileSystemResourceAccessor episodeNfoFsra; NfoSeriesEpisodeReader episodeNfoReader = null; bool episodeDetailsFound = false; if (TryGetEpisodeNfoSResourceAccessor(miNumber, mediaItemAccessor as IFileSystemResourceAccessor, out episodeNfoFsra)) { episodeDetailsFound = true; // Now we (asynchronously) extract the metadata into a stub object. // If no metadata was found, nothing can be stored in the MediaItemAspects. episodeNfoReader = new NfoSeriesEpisodeReader(_debugLogger, miNumber, forceQuickMode, isStub, _httpClient, _settings); using (episodeNfoFsra) { if (!await episodeNfoReader.TryReadMetadataAsync(episodeNfoFsra).ConfigureAwait(false)) { _debugLogger.Warn("[#{0}]: No valid metadata found in episode nfo-file", miNumber); return(false); } } } // Then we try to find an IFileSystemResourceAccessor pointing to the series nfo-file. IFileSystemResourceAccessor seriesNfoFsra; if (TryGetSeriesNfoSResourceAccessor(miNumber, mediaItemAccessor as IFileSystemResourceAccessor, out seriesNfoFsra)) { // If we found one, we (asynchronously) extract the metadata into a stub object and, if metadata was found, // we store it into the episodeNfoReader so that the latter can store metadata from series and episode level into the MediaItemAspects. var seriesNfoReader = new NfoSeriesReader(_debugLogger, miNumber, forceQuickMode, !episodeDetailsFound, isStub, _httpClient, _settings); using (seriesNfoFsra) { if (await seriesNfoReader.TryReadMetadataAsync(seriesNfoFsra).ConfigureAwait(false)) { //Check reimport if (extractedAspectData.ContainsKey(ReimportAspect.ASPECT_ID)) { SeriesInfo reimport = new SeriesInfo(); reimport.FromMetadata(extractedAspectData); if (!VerifySeriesReimport(seriesNfoReader, reimport)) { ServiceRegistration.Get <ILogger>().Info("NfoSeriesMetadataExtractor: Nfo series metadata from resource '{0}' ignored because it does not match reimport {1}", mediaItemAccessor, reimport); return(false); } } Stubs.SeriesStub series = seriesNfoReader.GetSeriesStubs().FirstOrDefault(); // Check if episode should be found if (isStub || !episodeDetailsFound) { if (series != null && series.Episodes?.Count > 0) { List <Stubs.SeriesEpisodeStub> episodeStubs = null; if (extractedAspectData.ContainsKey(EpisodeAspect.ASPECT_ID)) { int? seasonNo = 0; IEnumerable episodes; if (MediaItemAspect.TryGetAttribute(extractedAspectData, EpisodeAspect.ATTR_SEASON, out seasonNo) && MediaItemAspect.TryGetAttribute(extractedAspectData, EpisodeAspect.ATTR_EPISODE, out episodes)) { List <int> episodeNos = new List <int>(); CollectionUtils.AddAll(episodeNos, episodes.Cast <int>()); if (seasonNo.HasValue && episodeNos.Count > 0) { episodeStubs = series.Episodes.Where(e => e.Season == seasonNo.Value && episodeNos.Intersect(e.Episodes).Any()).ToList(); } } } else { string title = null; if (MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, out title)) { Regex regex = new Regex(@"(?<series>[^\\]+).S(?<seasonnum>\d+)[\s|\.|\-|_]{0,1}E((?<episodenum>\d+)_?)+(?<episode>.*)"); Match match = regex.Match(title); if (match.Success && match.Groups["seasonnum"].Length > 0 && match.Groups["episodenum"].Length > 0) { episodeStubs = series.Episodes.Where(e => e.Season == Convert.ToInt32(match.Groups["seasonnum"].Value) && e.Episodes.Contains(Convert.ToInt32(match.Groups["episodenum"].Value))).ToList(); } } } if (episodeStubs != null && episodeStubs.Count > 0) { Stubs.SeriesEpisodeStub mergedEpisode = null; if (isStub) { if (episodeStubs.Count == 1) { mergedEpisode = episodeStubs.First(); } else { Stubs.SeriesEpisodeStub episode = episodeStubs.First(); mergedEpisode = new Stubs.SeriesEpisodeStub(); mergedEpisode.Actors = episode.Actors; mergedEpisode.Aired = episode.Aired; mergedEpisode.Credits = episode.Credits; mergedEpisode.Director = episode.Director; mergedEpisode.DisplayEpisode = episode.DisplayEpisode; mergedEpisode.DisplaySeason = episode.DisplaySeason; mergedEpisode.EpBookmark = episode.EpBookmark; mergedEpisode.FileInfo = episode.FileInfo; mergedEpisode.LastPlayed = episode.LastPlayed; mergedEpisode.Mpaa = episode.Mpaa; mergedEpisode.PlayCount = episode.PlayCount; mergedEpisode.Premiered = episode.Premiered; mergedEpisode.ProductionCodeNumber = episode.ProductionCodeNumber; mergedEpisode.ResumePosition = episode.ResumePosition; mergedEpisode.Season = episode.Season; mergedEpisode.Sets = episode.Sets; mergedEpisode.ShowTitle = episode.ShowTitle; mergedEpisode.Status = episode.Status; mergedEpisode.Studio = episode.Studio; mergedEpisode.Tagline = episode.Tagline; mergedEpisode.Thumb = episode.Thumb; mergedEpisode.Top250 = episode.Top250; mergedEpisode.Trailer = episode.Trailer; mergedEpisode.Watched = episode.Watched; mergedEpisode.Year = episode.Year; mergedEpisode.Id = episode.Id; mergedEpisode.UniqueId = episode.UniqueId; //Merge episodes mergedEpisode.Title = string.Join("; ", episodeStubs.OrderBy(e => e.Episodes.First()).Select(e => e.Title).ToArray()); mergedEpisode.Rating = episodeStubs.Where(e => e.Rating.HasValue).Sum(e => e.Rating.Value) / episodeStubs.Where(e => e.Rating.HasValue).Count(); // Average rating mergedEpisode.Votes = episodeStubs.Where(e => e.Votes.HasValue).Sum(e => e.Votes.Value) / episodeStubs.Where(e => e.Votes.HasValue).Count(); mergedEpisode.Runtime = TimeSpan.FromSeconds(episodeStubs.Where(e => e.Runtime.HasValue).Sum(e => e.Runtime.Value.TotalSeconds)); mergedEpisode.Plot = string.Join("\r\n\r\n", episodeStubs.OrderBy(e => e.Episodes.First()). Select(e => string.Format("{0,02}) {1}", e.Episodes.First(), e.Plot)).ToArray()); mergedEpisode.Outline = string.Join("\r\n\r\n", episodeStubs.OrderBy(e => e.Episodes.First()). Select(e => string.Format("{0,02}) {1}", e.Episodes.First(), e.Outline)).ToArray()); mergedEpisode.Episodes = new HashSet <int>(episodeStubs.SelectMany(x => x.Episodes).ToList()); mergedEpisode.DvdEpisodes = new HashSet <decimal>(episodeStubs.SelectMany(x => x.DvdEpisodes).ToList()); } IList <MultipleMediaItemAspect> providerResourceAspects; if (MediaItemAspect.TryGetAspects(extractedAspectData, ProviderResourceAspect.Metadata, out providerResourceAspects)) { MultipleMediaItemAspect providerResourceAspect = providerResourceAspects.First(pa => pa.GetAttributeValue <int>(ProviderResourceAspect.ATTR_TYPE) == ProviderResourceAspect.TYPE_STUB); string mime = null; if (mergedEpisode.FileInfo != null && mergedEpisode.FileInfo.Count > 0) { mime = MimeTypeDetector.GetMimeTypeFromExtension("file" + mergedEpisode.FileInfo.First().Container); } if (mime != null) { providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_MIME_TYPE, mime); } } MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, string.Format("{0} S{1:00}{2} {3}", series.ShowTitle, mergedEpisode.Season, mergedEpisode.Episodes.Select(e => "E" + e.ToString("00")), mergedEpisode.Title)); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_SORT_TITLE, BaseInfo.GetSortTitle(mergedEpisode.Title)); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, mergedEpisode.Premiered.HasValue ? mergedEpisode.Premiered.Value : mergedEpisode.Year.HasValue ? mergedEpisode.Year.Value : (DateTime?)null); if (mergedEpisode.FileInfo != null && mergedEpisode.FileInfo.Count > 0) { extractedAspectData.Remove(VideoStreamAspect.ASPECT_ID); extractedAspectData.Remove(VideoAudioStreamAspect.ASPECT_ID); extractedAspectData.Remove(SubtitleAspect.ASPECT_ID); StubParser.ParseFileInfo(extractedAspectData, mergedEpisode.FileInfo, mergedEpisode.Title); } } episodeNfoReader = new NfoSeriesEpisodeReader(_debugLogger, miNumber, forceQuickMode, isStub, _httpClient, _settings); episodeNfoReader.SetEpisodeStubs(new List <Stubs.SeriesEpisodeStub> { mergedEpisode }); } } } if (series != null) { if (episodeNfoReader != null) { episodeNfoReader.SetSeriesStubs(new List <Stubs.SeriesStub> { series }); // Then we store the found metadata in the MediaItemAspects. If we only found metadata that is // not (yet) supported by our MediaItemAspects, this MetadataExtractor returns false. if (!episodeNfoReader.TryWriteMetadata(extractedAspectData)) { _debugLogger.Warn("[#{0}]: No metadata was written into MediaItemsAspects", miNumber); return(false); } } else { EpisodeInfo episode = new EpisodeInfo(); if (series.Id.HasValue) { episode.SeriesTvdbId = series.Id.Value; } if (series.Premiered.HasValue) { episode.SeriesFirstAired = series.Premiered.Value; } episode.SeriesName = series.ShowTitle; episode.SetMetadata(extractedAspectData); } } } else { _debugLogger.Warn("[#{0}]: No valid metadata found in series nfo-file", miNumber); } } } else if (episodeNfoReader != null) { //Check reimport if (extractedAspectData.ContainsKey(ReimportAspect.ASPECT_ID)) { EpisodeInfo reimport = new EpisodeInfo(); reimport.FromMetadata(extractedAspectData); if (!VerifyEpisodeReimport(episodeNfoReader, reimport)) { ServiceRegistration.Get <ILogger>().Info("NfoSeriesMetadataExtractor: Nfo episode metadata from resource '{0}' ignored because it does not match reimport {1}", mediaItemAccessor, reimport); return(false); } } // Then we store the found metadata in the MediaItemAspects. If we only found metadata that is // not (yet) supported by our MediaItemAspects, this MetadataExtractor returns false. if (!episodeNfoReader.TryWriteMetadata(extractedAspectData)) { _debugLogger.Warn("[#{0}]: No metadata was written into MediaItemsAspects", miNumber); return(false); } } _debugLogger.Info("[#{0}]: Successfully finished extracting metadata", miNumber); ServiceRegistration.Get <ILogger>().Debug("NfoSeriesMetadataExtractor: Assigned nfo episode metadata for resource '{0}'", mediaItemAccessor); return(true); } catch (Exception e) { ServiceRegistration.Get <ILogger>().Warn("NfoSeriesMetadataExtractor: Exception while extracting metadata for resource '{0}'; enable debug logging for more details.", mediaItemAccessor); _debugLogger.Error("[#{0}]: Exception while extracting metadata", e, miNumber); return(false); } }
protected async Task ExtractEpisodeFanArt(Guid mediaItemId, IDictionary <Guid, IList <MediaItemAspect> > aspects) { if (BaseInfo.IsVirtualResource(aspects)) { return; } IResourceLocator mediaItemLocator = GetResourceLocator(aspects); if (mediaItemLocator == null) { return; } //Whether local fanart should be stored in the fanart cache bool shouldCacheLocal = ShouldCacheLocalFanArt(mediaItemLocator.NativeResourcePath, SeriesMetadataExtractor.CacheLocalFanArt, SeriesMetadataExtractor.CacheOfflineFanArt); if (!shouldCacheLocal && SeriesMetadataExtractor.SkipFanArtDownload) { return; //Nothing to do } EpisodeInfo episodeInfo = new EpisodeInfo(); episodeInfo.FromMetadata(aspects); //Episode fanart if (AddToCache(mediaItemId)) { if (shouldCacheLocal) { await ExtractEpisodeFolderFanArt(mediaItemLocator, mediaItemId, episodeInfo.ToString()).ConfigureAwait(false); } if (!SeriesMetadataExtractor.SkipFanArtDownload) { await OnlineMatcherService.Instance.DownloadSeriesFanArtAsync(mediaItemId, episodeInfo).ConfigureAwait(false); } } //Actor fanart may be stored in the season or series directory, so get the actors now IList <Tuple <Guid, string> > actors = null; if (MediaItemAspect.TryGetAspect(aspects, VideoAspect.Metadata, out SingleMediaItemAspect videoAspect)) { var actorNames = videoAspect.GetCollectionAttribute <string>(VideoAspect.ATTR_ACTORS); if (actorNames != null) { RelationshipExtractorUtils.TryGetMappedLinkedIds(PersonAspect.ROLE_ACTOR, aspects, actorNames.ToList(), out actors); } } //Take advantage of the audio language being known and download season and series too //Season fanart if (RelationshipExtractorUtils.TryGetLinkedId(SeasonAspect.ROLE_SEASON, aspects, out Guid seasonMediaItemId) && AddToCache(seasonMediaItemId)) { SeasonInfo seasonInfo = episodeInfo.CloneBasicInstance <SeasonInfo>(); if (shouldCacheLocal) { await ExtractSeasonFolderFanArt(mediaItemLocator, seasonMediaItemId, seasonInfo.ToString(), seasonInfo.SeasonNumber, actors).ConfigureAwait(false); } if (!SeriesMetadataExtractor.SkipFanArtDownload) { await OnlineMatcherService.Instance.DownloadSeriesFanArtAsync(seasonMediaItemId, seasonInfo).ConfigureAwait(false); } } //Series fanart if (RelationshipExtractorUtils.TryGetLinkedId(SeriesAspect.ROLE_SERIES, aspects, out Guid seriesMediaItemId) && AddToCache(seriesMediaItemId)) { SeriesInfo seriesInfo = episodeInfo.CloneBasicInstance <SeriesInfo>(); if (shouldCacheLocal) { await ExtractSeriesFolderFanArt(mediaItemLocator, seriesMediaItemId, seriesInfo.ToString(), actors).ConfigureAwait(false); } if (!SeriesMetadataExtractor.SkipFanArtDownload) { await OnlineMatcherService.Instance.DownloadSeriesFanArtAsync(seriesMediaItemId, seriesInfo).ConfigureAwait(false); } } }
public bool TryMerge(IDictionary <Guid, IList <MediaItemAspect> > extractedAspects, IDictionary <Guid, IList <MediaItemAspect> > existingAspects) { try { EpisodeInfo existing = new EpisodeInfo(); EpisodeInfo extracted = new EpisodeInfo(); //Extracted aspects IList <MultipleMediaItemAspect> providerResourceAspects; if (!MediaItemAspect.TryGetAspects(extractedAspects, ProviderResourceAspect.Metadata, out providerResourceAspects)) { return(false); } //Existing aspects IList <MultipleMediaItemAspect> existingProviderResourceAspects; MediaItemAspect.TryGetAspects(existingAspects, ProviderResourceAspect.Metadata, out existingProviderResourceAspects); //Don't merge virtual resources if (!providerResourceAspects.Where(p => p.GetAttributeValue <int>(ProviderResourceAspect.ATTR_TYPE) == ProviderResourceAspect.TYPE_VIRTUAL).Any()) { //Replace if existing is a virtual resource if (existingProviderResourceAspects.Where(p => p.GetAttributeValue <int>(ProviderResourceAspect.ATTR_TYPE) == ProviderResourceAspect.TYPE_VIRTUAL).Any()) { //Don't allow merge of subtitles into virtual item if (extractedAspects.ContainsKey(SubtitleAspect.ASPECT_ID) && !extractedAspects.ContainsKey(VideoStreamAspect.ASPECT_ID)) { return(false); } MediaItemAspect.SetAttribute(existingAspects, MediaAspect.ATTR_ISVIRTUAL, false); MediaItemAspect.SetAttribute(existingAspects, MediaAspect.ATTR_ISSTUB, providerResourceAspects.Where(p => p.GetAttributeValue <int>(ProviderResourceAspect.ATTR_TYPE) == ProviderResourceAspect.TYPE_STUB).Any()); existingAspects.Remove(ProviderResourceAspect.ASPECT_ID); foreach (Guid aspect in extractedAspects.Keys) { if (!existingAspects.ContainsKey(aspect)) { existingAspects.Add(aspect, extractedAspects[aspect]); } } existing.FromMetadata(existingAspects); extracted.FromMetadata(extractedAspects); existing.MergeWith(extracted, true); existing.SetMetadata(existingAspects); return(true); } //Merge if (providerResourceAspects.Where(p => p.GetAttributeValue <int>(ProviderResourceAspect.ATTR_TYPE) == ProviderResourceAspect.TYPE_STUB).Any()) { if (providerResourceAspects.Where(p => p.GetAttributeValue <int>(ProviderResourceAspect.ATTR_TYPE) == ProviderResourceAspect.TYPE_STUB).Any() || existingProviderResourceAspects.Where(p => p.GetAttributeValue <int>(ProviderResourceAspect.ATTR_TYPE) == ProviderResourceAspect.TYPE_STUB).Any()) { MediaItemAspect.SetAttribute(existingAspects, MediaAspect.ATTR_ISVIRTUAL, false); MediaItemAspect.SetAttribute(existingAspects, MediaAspect.ATTR_ISSTUB, true); if (!ResourceAspectMerger.MergeVideoResourceAspects(extractedAspects, existingAspects)) { return(false); } } } } existing.FromMetadata(existingAspects); extracted.FromMetadata(extractedAspects); existing.MergeWith(extracted, true); existing.SetMetadata(existingAspects); if (!ResourceAspectMerger.MergeVideoResourceAspects(extractedAspects, existingAspects)) { return(false); } return(true); } catch (Exception e) { // Only log at the info level here - And simply return false. This lets the caller know that we // couldn't perform our task here. ServiceRegistration.Get <ILogger>().Info("EpisodeMergeHandler: Exception merging resources (Text: '{0}')", e.Message); return(false); } }
protected bool ExtractSeriesData(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData, bool importOnly) { // VideoAspect must be present to be sure it is actually a video resource. if (!extractedAspectData.ContainsKey(VideoStreamAspect.ASPECT_ID) && !extractedAspectData.ContainsKey(SubtitleAspect.ASPECT_ID)) { return(false); } if (extractedAspectData.ContainsKey(SubtitleAspect.ASPECT_ID) && !importOnly) { return(false); //Subtitles can only be imported not refreshed } bool refresh = false; if (extractedAspectData.ContainsKey(EpisodeAspect.ASPECT_ID)) { refresh = true; } EpisodeInfo episodeInfo = new EpisodeInfo(); if (refresh) { episodeInfo.FromMetadata(extractedAspectData); } ISeriesRelationshipExtractor.UpdateEpisodeSeries(extractedAspectData, episodeInfo); if (!episodeInfo.IsBaseInfoPresent) { string title = null; int seasonNumber; SingleMediaItemAspect episodeAspect; MediaItemAspect.TryGetAspect(extractedAspectData, EpisodeAspect.Metadata, out episodeAspect); IEnumerable <int> episodeNumbers; if (MediaItemAspect.TryGetAttribute(extractedAspectData, EpisodeAspect.ATTR_SERIES_NAME, out title) && MediaItemAspect.TryGetAttribute(extractedAspectData, EpisodeAspect.ATTR_SEASON, out seasonNumber) && (episodeNumbers = episodeAspect.GetCollectionAttribute <int>(EpisodeAspect.ATTR_EPISODE)) != null) { episodeInfo.SeriesName = title; episodeInfo.SeasonNumber = seasonNumber; episodeInfo.EpisodeNumbers.Clear(); episodeNumbers.ToList().ForEach(n => episodeInfo.EpisodeNumbers.Add(n)); } } // If there was no complete match, yet, try to get extended information out of matroska files) if (!episodeInfo.IsBaseInfoPresent || !episodeInfo.HasExternalId) { try { MatroskaMatcher matroskaMatcher = new MatroskaMatcher(); if (matroskaMatcher.MatchSeries(lfsra, episodeInfo)) { ServiceRegistration.Get <ILogger>().Debug("ExtractSeriesData: Found EpisodeInfo by MatroskaMatcher for {0}, IMDB {1}, TVDB {2}, TMDB {3}, AreReqiredFieldsFilled {4}", episodeInfo.SeriesName, episodeInfo.SeriesImdbId, episodeInfo.SeriesTvdbId, episodeInfo.SeriesMovieDbId, episodeInfo.IsBaseInfoPresent); } } catch (Exception ex) { ServiceRegistration.Get <ILogger>().Debug("ExtractSeriesData: Exception reading matroska tags for '{0}'", ex, lfsra.CanonicalLocalResourcePath); } } // If no information was found before, try name matching if (!episodeInfo.IsBaseInfoPresent) { // Try to match series from folder and file naming SeriesMatcher seriesMatcher = new SeriesMatcher(); seriesMatcher.MatchSeries(lfsra, episodeInfo); } //Prepare online search improvements if (episodeInfo.SeriesFirstAired == null) { EpisodeInfo tempEpisodeInfo = new EpisodeInfo(); SeriesMatcher seriesMatcher = new SeriesMatcher(); seriesMatcher.MatchSeries(lfsra, tempEpisodeInfo); if (tempEpisodeInfo.SeriesFirstAired.HasValue) { episodeInfo.SeriesFirstAired = tempEpisodeInfo.SeriesFirstAired; } } if (string.IsNullOrEmpty(episodeInfo.SeriesAlternateName)) { var mediaItemPath = lfsra.CanonicalLocalResourcePath; var seriesMediaItemDirectoryPath = ResourcePathHelper.Combine(mediaItemPath, "../../"); episodeInfo.SeriesAlternateName = seriesMediaItemDirectoryPath.FileName; } if (episodeInfo.Languages.Count == 0) { IList <MultipleMediaItemAspect> audioAspects; if (MediaItemAspect.TryGetAspects(extractedAspectData, VideoAudioStreamAspect.Metadata, out audioAspects)) { foreach (MultipleMediaItemAspect aspect in audioAspects) { string language = (string)aspect.GetAttributeValue(VideoAudioStreamAspect.ATTR_AUDIOLANGUAGE); if (!string.IsNullOrEmpty(language) && !episodeInfo.Languages.Contains(language)) { episodeInfo.Languages.Add(language); } } } } episodeInfo.AssignNameId(); if (SkipOnlineSearches && !SkipFanArtDownload) { EpisodeInfo tempInfo = episodeInfo.Clone(); OnlineMatcherService.Instance.FindAndUpdateEpisode(tempInfo, importOnly); episodeInfo.CopyIdsFrom(tempInfo); episodeInfo.HasChanged = tempInfo.HasChanged; } else if (!SkipOnlineSearches) { OnlineMatcherService.Instance.FindAndUpdateEpisode(episodeInfo, importOnly); } //Send it to the videos section if (!SkipOnlineSearches && !episodeInfo.HasExternalId) { return(false); } if (refresh) { if ((IncludeActorDetails && !BaseInfo.HasRelationship(extractedAspectData, PersonAspect.ROLE_ACTOR) && episodeInfo.Actors.Count > 0) || (IncludeCharacterDetails && !BaseInfo.HasRelationship(extractedAspectData, CharacterAspect.ROLE_CHARACTER) && episodeInfo.Characters.Count > 0) || (IncludeDirectorDetails && !BaseInfo.HasRelationship(extractedAspectData, PersonAspect.ROLE_DIRECTOR) && episodeInfo.Directors.Count > 0) || (IncludeWriterDetails && !BaseInfo.HasRelationship(extractedAspectData, PersonAspect.ROLE_WRITER) && episodeInfo.Writers.Count > 0) || (!BaseInfo.HasRelationship(extractedAspectData, SeriesAspect.ROLE_SERIES) && !episodeInfo.SeriesName.IsEmpty) || (!BaseInfo.HasRelationship(extractedAspectData, SeasonAspect.ROLE_SEASON) && episodeInfo.SeasonNumber.HasValue)) { episodeInfo.HasChanged = true; } } if (!episodeInfo.HasChanged && !importOnly) { return(false); } episodeInfo.SetMetadata(extractedAspectData); return(episodeInfo.IsBaseInfoPresent); }
protected async Task <bool> ExtractSeriesDataAsync(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData) { // VideoAspect must be present to be sure it is actually a video resource. if (!extractedAspectData.ContainsKey(VideoAspect.ASPECT_ID) && !extractedAspectData.ContainsKey(SubtitleAspect.ASPECT_ID)) { return(false); } bool isReimport = extractedAspectData.ContainsKey(ReimportAspect.ASPECT_ID); EpisodeInfo episodeInfo = new EpisodeInfo(); episodeInfo.FromMetadata(extractedAspectData); if (!isReimport) //Ignore file based information for reimports because they might be the cause of the wrong match { // If there was no complete match, yet, try to get extended information out of matroska files) if (!episodeInfo.IsBaseInfoPresent || !episodeInfo.HasExternalId) { try { MatroskaMatcher matroskaMatcher = new MatroskaMatcher(); if (await matroskaMatcher.MatchSeriesAsync(lfsra, episodeInfo).ConfigureAwait(false)) { ServiceRegistration.Get <ILogger>().Debug("ExtractSeriesData: Found EpisodeInfo by MatroskaMatcher for {0}, IMDB {1}, TVDB {2}, TMDB {3}, AreReqiredFieldsFilled {4}", episodeInfo.SeriesName, episodeInfo.SeriesImdbId, episodeInfo.SeriesTvdbId, episodeInfo.SeriesMovieDbId, episodeInfo.IsBaseInfoPresent); } } catch (Exception ex) { ServiceRegistration.Get <ILogger>().Debug("ExtractSeriesData: Exception reading matroska tags for '{0}'", ex, lfsra.CanonicalLocalResourcePath); } } // If no information was found before, try name matching if (!episodeInfo.IsBaseInfoPresent) { // Try to match series from folder and file naming SeriesMatcher seriesMatcher = new SeriesMatcher(); seriesMatcher.MatchSeries(lfsra, episodeInfo); } //Prepare online search improvements if (episodeInfo.SeriesFirstAired == null) { EpisodeInfo tempEpisodeInfo = new EpisodeInfo(); SeriesMatcher seriesMatcher = new SeriesMatcher(); seriesMatcher.MatchSeries(lfsra, tempEpisodeInfo); if (tempEpisodeInfo.SeriesFirstAired.HasValue) { episodeInfo.SeriesFirstAired = tempEpisodeInfo.SeriesFirstAired; } } if (string.IsNullOrEmpty(episodeInfo.SeriesAlternateName)) { var mediaItemPath = lfsra.CanonicalLocalResourcePath; var seriesMediaItemDirectoryPath = ResourcePathHelper.Combine(mediaItemPath, "../../"); episodeInfo.SeriesAlternateName = seriesMediaItemDirectoryPath.FileName; } } if (episodeInfo.Languages.Count == 0) { IList <MultipleMediaItemAspect> audioAspects; if (MediaItemAspect.TryGetAspects(extractedAspectData, VideoAudioStreamAspect.Metadata, out audioAspects)) { foreach (MultipleMediaItemAspect aspect in audioAspects) { string language = (string)aspect.GetAttributeValue(VideoAudioStreamAspect.ATTR_AUDIOLANGUAGE); if (!string.IsNullOrEmpty(language) && !episodeInfo.Languages.Contains(language)) { episodeInfo.Languages.Add(language); } } } } if (SkipOnlineSearches && !SkipFanArtDownload) { EpisodeInfo tempInfo = episodeInfo.Clone(); await OnlineMatcherService.Instance.FindAndUpdateEpisodeAsync(tempInfo).ConfigureAwait(false); episodeInfo.CopyIdsFrom(tempInfo); episodeInfo.HasChanged = tempInfo.HasChanged; } else if (!SkipOnlineSearches) { await OnlineMatcherService.Instance.FindAndUpdateEpisodeAsync(episodeInfo).ConfigureAwait(false); } if (episodeInfo.EpisodeName.IsEmpty) { if (episodeInfo.EpisodeNumbers.Any()) { episodeInfo.EpisodeName = $"E{episodeInfo.EpisodeNumbers.First().ToString("000")}"; } } //Send it to the videos section if (!SkipOnlineSearches && !episodeInfo.HasExternalId) { return(false); } if (episodeInfo.EpisodeNameSort.IsEmpty) { if (!episodeInfo.SeriesName.IsEmpty && episodeInfo.SeasonNumber.HasValue && episodeInfo.DvdEpisodeNumbers.Any()) { episodeInfo.EpisodeNameSort = $"{episodeInfo.SeriesName.Text} S{episodeInfo.SeasonNumber.Value.ToString("00")}E{episodeInfo.DvdEpisodeNumbers.First().ToString("000.000")}"; } if (!episodeInfo.SeriesName.IsEmpty && episodeInfo.SeasonNumber.HasValue && episodeInfo.EpisodeNumbers.Any()) { episodeInfo.EpisodeNameSort = $"{episodeInfo.SeriesName.Text} S{episodeInfo.SeasonNumber.Value.ToString("00")}E{episodeInfo.EpisodeNumbers.First().ToString("000")}"; } else if (!episodeInfo.EpisodeName.IsEmpty) { episodeInfo.EpisodeNameSort = BaseInfo.GetSortTitle(episodeInfo.EpisodeName.Text); } } episodeInfo.SetMetadata(extractedAspectData); return(episodeInfo.IsBaseInfoPresent); }
public async Task <IList <MediaItemSearchResult> > SearchForMatchesAsync(IDictionary <Guid, IList <MediaItemAspect> > searchAspectData, ICollection <string> searchCategories) { try { if (!(searchCategories?.Contains(MEDIA_CATEGORY_NAME_SERIES) ?? true)) { return(null); } string searchData = null; var reimportAspect = MediaItemAspect.GetAspect(searchAspectData, ReimportAspect.Metadata); if (reimportAspect != null) { searchData = reimportAspect.GetAttributeValue <string>(ReimportAspect.ATTR_SEARCH); } ServiceRegistration.Get <ILogger>().Debug("SeriesMetadataExtractor: Search aspects to use: '{0}'", string.Join(",", searchAspectData.Keys)); //Prepare search info EpisodeInfo episodeSearchinfo = null; SeriesInfo seriesSearchinfo = null; List <MediaItemSearchResult> searchResults = new List <MediaItemSearchResult>(); if (!string.IsNullOrEmpty(searchData)) { if (searchAspectData.ContainsKey(VideoAspect.ASPECT_ID)) { EpisodeInfo episode = new EpisodeInfo(); episode.FromMetadata(searchAspectData); episodeSearchinfo = new EpisodeInfo(); episodeSearchinfo.SeasonNumber = episode.SeasonNumber; episodeSearchinfo.EpisodeNumbers = episode.EpisodeNumbers.ToList(); if (searchData.StartsWith("tt", StringComparison.InvariantCultureIgnoreCase) && !searchData.Contains(" ") && int.TryParse(searchData.Substring(2), out int id)) { episodeSearchinfo.SeriesImdbId = searchData; } else if (!searchData.Contains(" ") && int.TryParse(searchData, out int tvDbSeriesId)) { episodeSearchinfo.SeriesTvdbId = tvDbSeriesId; } else //Fallabck to name search { searchData = searchData.Trim(); SeriesMatcher seriesMatcher = new SeriesMatcher(); //Add extension to simulate a file name which the matcher expects EpisodeInfo tempEpisodeInfo = new EpisodeInfo(); tempEpisodeInfo.SeasonNumber = episode.SeasonNumber; if (seriesMatcher.MatchSeries(searchData + ".ext", tempEpisodeInfo)) { episodeSearchinfo.SeriesName = tempEpisodeInfo.SeriesName; episodeSearchinfo.SeasonNumber = tempEpisodeInfo.SeasonNumber; episodeSearchinfo.EpisodeNumbers = tempEpisodeInfo.EpisodeNumbers.ToList(); } } ServiceRegistration.Get <ILogger>().Debug("SeriesMetadataExtractor: Searching for episode matches on search: '{0}'", searchData); } else if (searchAspectData.ContainsKey(SeriesAspect.ASPECT_ID)) { seriesSearchinfo = new SeriesInfo(); if (searchData.StartsWith("tt", StringComparison.InvariantCultureIgnoreCase) && !searchData.Contains(" ") && int.TryParse(searchData.Substring(2), out int id)) { seriesSearchinfo.ImdbId = searchData; } else if (!searchData.Contains(" ") && int.TryParse(searchData, out int tvDbSeriesId)) { seriesSearchinfo.TvdbId = tvDbSeriesId; } else //Fallabck to name search { searchData = searchData.Trim(); EpisodeInfo tempEpisodeInfo = new EpisodeInfo(); tempEpisodeInfo.SeasonNumber = 1; SeriesMatcher seriesMatcher = new SeriesMatcher(); //Add extension to simulate a file name which the matcher expects seriesMatcher.MatchSeries(searchData + " S01E01.ext", tempEpisodeInfo); if (tempEpisodeInfo.SeriesFirstAired.HasValue) { seriesSearchinfo.SeriesName = tempEpisodeInfo.SeriesName; seriesSearchinfo.FirstAired = tempEpisodeInfo.SeriesFirstAired; } else { seriesSearchinfo.SeriesName = searchData; } ServiceRegistration.Get <ILogger>().Debug("SeriesMetadataExtractor: Searching for series matches on search: '{0}'", searchData); } } } else { if (searchAspectData.ContainsKey(VideoAspect.ASPECT_ID)) { episodeSearchinfo = new EpisodeInfo(); episodeSearchinfo.FromMetadata(searchAspectData); ServiceRegistration.Get <ILogger>().Debug("SeriesMetadataExtractor: Searching for episode matches on aspects"); } else if (searchAspectData.ContainsKey(SeriesAspect.ASPECT_ID)) { seriesSearchinfo = new SeriesInfo(); seriesSearchinfo.FromMetadata(searchAspectData); ServiceRegistration.Get <ILogger>().Debug("SeriesMetadataExtractor: Searching for series matches on aspects"); } } //Perform online search if (episodeSearchinfo != null) { List <int> epNos = new List <int>(episodeSearchinfo.EpisodeNumbers.OrderBy(e => e)); var matches = await OnlineMatcherService.Instance.FindMatchingEpisodesAsync(episodeSearchinfo).ConfigureAwait(false); ServiceRegistration.Get <ILogger>().Debug("SeriesMetadataExtractor: Episode search returned {0} matches", matches.Count()); if (epNos.Count > 1) { //Check if double episode is in the search results if (!matches.Any(e => e.EpisodeNumbers.SequenceEqual(epNos))) { //Add a double episode if it's not var potentialEpisodes = new Dictionary <int, List <EpisodeInfo> >(); foreach (var episodeNo in epNos) { potentialEpisodes[episodeNo] = matches.Where(e => e.FirstEpisodeNumber == episodeNo && e.EpisodeNumbers.Count == 1).ToList(); } //Merge fitting episodes var mergedEpisodes = new List <EpisodeInfo>(); foreach (var episodeNo in epNos) { if (episodeNo == episodeSearchinfo.FirstEpisodeNumber) { foreach (var episode in potentialEpisodes[episodeNo]) { mergedEpisodes.Add(episode.Clone()); } } else { foreach (var mergedEpisode in mergedEpisodes) { var nextEpisode = potentialEpisodes[episodeNo].FirstOrDefault(e => e.SeriesTvdbId > 0 && e.SeriesTvdbId == mergedEpisode.SeriesTvdbId && e.SeasonNumber == mergedEpisode.SeasonNumber); if (nextEpisode == null) { nextEpisode = potentialEpisodes[episodeNo].FirstOrDefault(e => !string.IsNullOrEmpty(e.SeriesImdbId) && e.SeriesImdbId.Equals(mergedEpisode.SeriesImdbId, StringComparison.InvariantCultureIgnoreCase) && e.SeasonNumber == mergedEpisode.SeasonNumber); } if (nextEpisode == null) { nextEpisode = potentialEpisodes[episodeNo].FirstOrDefault(e => e.SeriesMovieDbId > 0 && e.SeriesMovieDbId == mergedEpisode.SeriesMovieDbId && e.SeasonNumber == mergedEpisode.SeasonNumber); } if (nextEpisode != null) { MergeEpisodeDetails(mergedEpisode, nextEpisode); } } } } //Add valid merged episodes to search result var list = matches.ToList(); var validMergedEpisodes = mergedEpisodes.Where(e => e.EpisodeNumbers.SequenceEqual(epNos)); list.AddRange(validMergedEpisodes); matches = list.AsEnumerable(); if (validMergedEpisodes.Count() > 0) { ServiceRegistration.Get <ILogger>().Debug("SeriesMetadataExtractor: Added {0} multi-episodes to matches", validMergedEpisodes.Count()); } } } foreach (var match in matches) { var result = new MediaItemSearchResult { Name = $"{match.SeriesName}{(match.SeriesFirstAired == null || match.SeriesName.Text.EndsWith($"({match.SeriesFirstAired.Value.Year})") ? "" : $" ({match.SeriesFirstAired.Value.Year})")}" + $" S{(match.SeasonNumber.HasValue ? match.SeasonNumber.Value.ToString("00") : "??")}{(match.EpisodeNumbers.Count > 0 ? string.Join("", match.EpisodeNumbers.Select(e => "E" + e.ToString("00"))) : "E??")}" + $"{(match.EpisodeName.IsEmpty ? "" : $": {match.EpisodeName.Text}")}", Description = match.Summary.IsEmpty ? "" : match.Summary.Text, }; //Add external Ids if (match.TvdbId > 0) { result.ExternalIds.Add("thetvdb.com", match.TvdbId.ToString()); } if (!string.IsNullOrEmpty(match.ImdbId)) { result.ExternalIds.Add("imdb.com", match.ImdbId); } if (match.MovieDbId > 0) { result.ExternalIds.Add("themoviedb.org", match.MovieDbId.ToString()); } //Assign aspects and remove unwanted aspects match.SetMetadata(result.AspectData, true); CleanReimportAspects(result.AspectData); searchResults.Add(result); } return(searchResults); } else if (seriesSearchinfo != null) { var matches = await OnlineMatcherService.Instance.FindMatchingSeriesAsync(seriesSearchinfo).ConfigureAwait(false); ServiceRegistration.Get <ILogger>().Debug("SeriesMetadataExtractor: Series search returned {0} matches", matches.Count()); foreach (var match in matches) { var result = new MediaItemSearchResult { Name = $"{match.SeriesName}{(match.FirstAired == null || match.SeriesName.Text.EndsWith($"({match.FirstAired.Value.Year})") ? "" : $" ({match.FirstAired.Value.Year})")}", Description = match.Description.IsEmpty ? "" : match.Description.Text, }; //Add external Ids if (match.TvdbId > 0) { result.ExternalIds.Add("thetvdb.com", match.TvdbId.ToString()); } if (!string.IsNullOrEmpty(match.ImdbId)) { result.ExternalIds.Add("imdb.com", match.ImdbId); } if (match.MovieDbId > 0) { result.ExternalIds.Add("themoviedb.org", match.MovieDbId.ToString()); } //Assign aspects and remove unwanted aspects match.SetMetadata(result.AspectData, true); CleanReimportAspects(result.AspectData); searchResults.Add(result); } return(searchResults); }
public bool TryExtractRelationships(IDictionary <Guid, IList <MediaItemAspect> > aspects, bool importOnly, out IList <RelationshipItem> extractedLinkedAspects) { extractedLinkedAspects = null; EpisodeInfo episodeInfo = new EpisodeInfo(); if (!episodeInfo.FromMetadata(aspects)) { return(false); } if (CheckCacheContains(episodeInfo)) { return(false); } SeriesInfo cachedSeries; Guid seriesId; SeriesInfo seriesInfo = episodeInfo.CloneBasicInstance <SeriesInfo>(); if (TryGetInfoFromCache(seriesInfo, out cachedSeries, out seriesId)) { seriesInfo = cachedSeries; } else if (!SeriesMetadataExtractor.SkipOnlineSearches) { OnlineMatcherService.Instance.UpdateSeries(seriesInfo, false, importOnly); } if (!BaseInfo.HasRelationship(aspects, LinkedRole)) { seriesInfo.HasChanged = true; //Force save if no relationship exists } if (!seriesInfo.HasChanged && !importOnly) { return(false); } AddToCheckCache(episodeInfo); extractedLinkedAspects = new List <RelationshipItem>(); IDictionary <Guid, IList <MediaItemAspect> > seriesAspects = new Dictionary <Guid, IList <MediaItemAspect> >(); seriesInfo.SetMetadata(seriesAspects); if (aspects.ContainsKey(EpisodeAspect.ASPECT_ID)) { bool episodeVirtual = true; if (MediaItemAspect.TryGetAttribute(aspects, MediaAspect.ATTR_ISVIRTUAL, false, out episodeVirtual)) { MediaItemAspect.SetAttribute(seriesAspects, MediaAspect.ATTR_ISVIRTUAL, episodeVirtual); } } if (!seriesAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { return(false); } if (seriesId != Guid.Empty) { extractedLinkedAspects.Add(new RelationshipItem(seriesAspects, seriesId)); } else { extractedLinkedAspects.Add(new RelationshipItem(seriesAspects, Guid.Empty)); } return(true); }
private void ExtractFanArt(Guid mediaItemId, IDictionary <Guid, IList <MediaItemAspect> > aspects, Guid?seriesMediaItemId, Guid?seasonMediaItemId, IDictionary <Guid, string> actorMediaItems) { if (aspects.ContainsKey(EpisodeAspect.ASPECT_ID)) { if (BaseInfo.IsVirtualResource(aspects)) { return; } EpisodeInfo episodeInfo = new EpisodeInfo(); episodeInfo.FromMetadata(aspects); bool forceFanart = !episodeInfo.IsRefreshed; SeasonInfo seasonInfo = episodeInfo.CloneBasicInstance <SeasonInfo>(); SeriesInfo seriesInfo = episodeInfo.CloneBasicInstance <SeriesInfo>(); ExtractLocalImages(aspects, mediaItemId, seriesMediaItemId, seasonMediaItemId, episodeInfo, seriesInfo, seasonInfo, actorMediaItems); if (!SeriesMetadataExtractor.SkipFanArtDownload) { OnlineMatcherService.Instance.DownloadSeriesFanArt(mediaItemId, episodeInfo, forceFanart); } //Take advantage of the audio language being known and download season and series too if (seasonMediaItemId.HasValue && !_checkCache.Contains(seasonMediaItemId.Value)) { _checkCache.Add(seasonMediaItemId.Value); if (!SeriesMetadataExtractor.SkipFanArtDownload) { OnlineMatcherService.Instance.DownloadSeriesFanArt(seasonMediaItemId.Value, seasonInfo, forceFanart); } } if (seriesMediaItemId.HasValue && !_checkCache.Contains(seriesMediaItemId.Value)) { _checkCache.Add(seriesMediaItemId.Value); if (!SeriesMetadataExtractor.SkipFanArtDownload) { OnlineMatcherService.Instance.DownloadSeriesFanArt(seriesMediaItemId.Value, seriesInfo, forceFanart); } } } else if (aspects.ContainsKey(PersonAspect.ASPECT_ID)) { PersonInfo personInfo = new PersonInfo(); personInfo.FromMetadata(aspects); if (personInfo.Occupation == PersonAspect.OCCUPATION_ACTOR || personInfo.Occupation == PersonAspect.OCCUPATION_DIRECTOR || personInfo.Occupation == PersonAspect.OCCUPATION_WRITER) { if (!SeriesMetadataExtractor.SkipFanArtDownload) { OnlineMatcherService.Instance.DownloadSeriesFanArt(mediaItemId, personInfo, !personInfo.IsRefreshed); } } } else if (aspects.ContainsKey(CharacterAspect.ASPECT_ID)) { CharacterInfo characterInfo = new CharacterInfo(); characterInfo.FromMetadata(aspects); if (!SeriesMetadataExtractor.SkipFanArtDownload) { OnlineMatcherService.Instance.DownloadSeriesFanArt(mediaItemId, characterInfo, !characterInfo.IsRefreshed); } } else if (aspects.ContainsKey(CompanyAspect.ASPECT_ID)) { CompanyInfo companyInfo = new CompanyInfo(); companyInfo.FromMetadata(aspects); if (companyInfo.Type == CompanyAspect.COMPANY_PRODUCTION || companyInfo.Type == CompanyAspect.COMPANY_TV_NETWORK) { if (!SeriesMetadataExtractor.SkipFanArtDownload) { OnlineMatcherService.Instance.DownloadSeriesFanArt(mediaItemId, companyInfo, !companyInfo.IsRefreshed); } } } }
protected async Task ExtractEpisodeFanArt(Guid mediaItemId, IDictionary <Guid, IList <MediaItemAspect> > aspects) { bool shouldCacheLocal = false; IResourceLocator mediaItemLocator = null; if (!BaseInfo.IsVirtualResource(aspects)) { mediaItemLocator = GetResourceLocator(aspects); //Whether local fanart should be stored in the fanart cache shouldCacheLocal = ShouldCacheLocalFanArt(mediaItemLocator.NativeResourcePath, SeriesMetadataExtractor.CacheLocalFanArt, SeriesMetadataExtractor.CacheOfflineFanArt); } if (mediaItemLocator == null) { return; } if (!shouldCacheLocal && SeriesMetadataExtractor.SkipFanArtDownload) { return; //Nothing to do } EpisodeInfo episodeInfo = new EpisodeInfo(); episodeInfo.FromMetadata(aspects); //Episode fanart if (AddToCache(mediaItemId)) { if (shouldCacheLocal) { await ExtractEpisodeFolderFanArt(mediaItemLocator, mediaItemId, episodeInfo.ToString()).ConfigureAwait(false); } if (!SeriesMetadataExtractor.SkipFanArtDownload) { await OnlineMatcherService.Instance.DownloadSeriesFanArtAsync(mediaItemId, episodeInfo).ConfigureAwait(false); } } //Actor fanart may be stored in the season or series directory, so get the actors now IList <Tuple <Guid, string> > actors = null; if (MediaItemAspect.TryGetAspect(aspects, VideoAspect.Metadata, out SingleMediaItemAspect videoAspect)) { var actorNames = videoAspect.GetCollectionAttribute <string>(VideoAspect.ATTR_ACTORS); if (actorNames != null) { RelationshipExtractorUtils.TryGetMappedLinkedIds(PersonAspect.ROLE_ACTOR, aspects, actorNames.ToList(), out actors); } } //Take advantage of the audio language being known and download season and series too //Season fanart if (RelationshipExtractorUtils.TryGetLinkedId(SeasonAspect.ROLE_SEASON, aspects, out Guid seasonMediaItemId) && AddToCache(seasonMediaItemId)) { SeasonInfo seasonInfo = episodeInfo.CloneBasicInstance <SeasonInfo>(); if (shouldCacheLocal) { await ExtractSeasonFolderFanArt(mediaItemLocator, seasonMediaItemId, seasonInfo.ToString(), seasonInfo.SeasonNumber, actors).ConfigureAwait(false); } if (!SeriesMetadataExtractor.SkipFanArtDownload) { await OnlineMatcherService.Instance.DownloadSeriesFanArtAsync(seasonMediaItemId, seasonInfo).ConfigureAwait(false); } } //Series fanart if (RelationshipExtractorUtils.TryGetLinkedId(SeriesAspect.ROLE_SERIES, aspects, out Guid seriesMediaItemId) && AddToCache(seriesMediaItemId)) { SeriesInfo seriesInfo = episodeInfo.CloneBasicInstance <SeriesInfo>(); if (shouldCacheLocal) { await ExtractSeriesFolderFanArt(mediaItemLocator, seriesMediaItemId, seriesInfo.ToString(), episodeInfo.SeasonNumber, actors).ConfigureAwait(false); } if (!SeriesMetadataExtractor.SkipFanArtDownload) { await OnlineMatcherService.Instance.DownloadSeriesFanArtAsync(seriesMediaItemId, seriesInfo).ConfigureAwait(false); } } //Find central actor information folder var seriesDirectory = ResourcePathHelper.Combine(mediaItemLocator.NativeResourcePath, "../../"); ResourcePath centralActorFolderPath = LocalFanartHelper.GetCentralPersonFolder(seriesDirectory, CentralPersonFolderType.SeriesActors); if (shouldCacheLocal && centralActorFolderPath != null && actors != null) { foreach (var actor in actors) { // Check if we already processed this actor if (!AddToCache(actor.Item1)) { continue; } // First get the ResourcePath of the central directory var artistFolderPath = ResourcePathHelper.Combine(centralActorFolderPath, $"{LocalFanartHelper.GetSafePersonFolderName(actor.Item2)}/"); using (IResourceAccessor accessor = new ResourceLocator(mediaItemLocator.NativeSystemId, artistFolderPath).CreateAccessor()) { if (accessor is IFileSystemResourceAccessor directoryAccessor) { FanArtPathCollection paths = new FanArtPathCollection(); List <ResourcePath> potentialFanArtFiles = LocalFanartHelper.GetPotentialFanArtFiles(directoryAccessor); ExtractAllFanArtImages(potentialFanArtFiles, paths); await SaveFolderImagesToCache(mediaItemLocator.NativeSystemId, paths, actor.Item1, actor.Item2).ConfigureAwait(false); } } } } }