public void CacheExtractedItem(Guid extractedItemId, IDictionary <Guid, IList <MediaItemAspect> > extractedAspects) { SeriesInfo series = new SeriesInfo(); series.FromMetadata(extractedAspects); AddToCache(extractedItemId, series, false); }
public async Task <bool> TryExtractRelationshipsAsync(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > aspects, IList <IDictionary <Guid, IList <MediaItemAspect> > > extractedLinkedAspects) { if (SeriesMetadataExtractor.OnlyLocalMedia) { return(false); } SeriesInfo seriesInfo = new SeriesInfo(); if (!seriesInfo.FromMetadata(aspects)) { return(false); } if (!SeriesMetadataExtractor.SkipOnlineSearches) { await OnlineMatcherService.Instance.UpdateSeriesAsync(seriesInfo, true, _category).ConfigureAwait(false); } foreach (EpisodeInfo episodeInfo in seriesInfo.Episodes) { episodeInfo.SeriesNameId = seriesInfo.NameId; IDictionary <Guid, IList <MediaItemAspect> > episodeAspects = new Dictionary <Guid, IList <MediaItemAspect> >(); episodeInfo.SetMetadata(episodeAspects); MediaItemAspect.SetAttribute(episodeAspects, MediaAspect.ATTR_ISVIRTUAL, true); if (episodeAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { extractedLinkedAspects.Add(episodeAspects); } } return(extractedLinkedAspects.Count > 0); }
public IDictionary <Guid, IList <MediaItemAspect> > GetBaseChildAspectsFromExistingAspects(IDictionary <Guid, IList <MediaItemAspect> > existingChildAspects, IDictionary <Guid, IList <MediaItemAspect> > existingParentAspects) { if (existingParentAspects.ContainsKey(SeriesAspect.ASPECT_ID)) { SeriesInfo series = new SeriesInfo(); series.FromMetadata(existingParentAspects); if (existingChildAspects.ContainsKey(SeasonAspect.ASPECT_ID)) { SeasonInfo season = new SeasonInfo(); season.FromMetadata(existingChildAspects); SeasonInfo basicSeason = series.CloneBasicInstance <SeasonInfo>(); basicSeason.SeasonNumber = season.SeasonNumber; IDictionary <Guid, IList <MediaItemAspect> > aspects = new Dictionary <Guid, IList <MediaItemAspect> >(); basicSeason.SetMetadata(aspects, true); return(aspects); } else if (existingChildAspects.ContainsKey(EpisodeAspect.ASPECT_ID)) { EpisodeInfo episode = new EpisodeInfo(); episode.FromMetadata(existingChildAspects); EpisodeInfo basicEpisode = series.CloneBasicInstance <EpisodeInfo>(); basicEpisode.SeasonNumber = episode.SeasonNumber; basicEpisode.EpisodeNumbers = episode.EpisodeNumbers.ToList(); IDictionary <Guid, IList <MediaItemAspect> > aspects = new Dictionary <Guid, IList <MediaItemAspect> >(); basicEpisode.SetMetadata(aspects, true); return(aspects); } } return(null); }
/// <summary> /// Constructor. /// </summary> public SeriesShowInfo(MediaItem mediaItem) { try { SeriesId = mediaItem.MediaItemId; SeriesInfo series = new SeriesInfo(); series.FromMetadata(mediaItem.Aspects); Summary = series.Description.Text; Title = series.SeriesName.Text; Actors = String.Join(", ", series.Actors); Genre = String.Join(", ", series.Genres.Select(g => g.Name)); AirDate = series.FirstAired.HasValue ? series.FirstAired.Value.ToLongDateString() : ""; Rating = Convert.ToString(series.Rating.RatingValue ?? 0); RatingCount = Convert.ToString(series.Rating.VoteCount ?? 0); Status = series.IsEnded ? "Ended" : "Running"; Certification = series.Certification; ImageName = Helper.GetImageBaseURL(mediaItem, FanArtMediaTypes.Series, FanArtTypes.Poster); } catch (Exception e) { ServiceRegistration.Get <ILogger>().Error("WifiRemote: Error getting series info", e); } }
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; if (!NfoSeriesMetadataExtractor.IncludeActorDetails) { return(false); } if (!importOnly) //Only during import { return(false); } SeriesInfo seriesInfo = new SeriesInfo(); if (!seriesInfo.FromMetadata(aspects)) { return(false); } if (!UpdatePersons(aspects, seriesInfo.Actors, true)) { return(false); } if (seriesInfo.Actors.Count == 0) { return(false); } extractedLinkedAspects = new List <RelationshipItem>(); foreach (PersonInfo person in seriesInfo.Actors) { person.AssignNameId(); person.HasChanged = seriesInfo.HasChanged; 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); }
public async Task <bool> TryExtractRelationshipsAsync(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > aspects, IList <IDictionary <Guid, IList <MediaItemAspect> > > extractedLinkedAspects) { if (!NfoSeriesMetadataExtractor.IncludeActorDetails) { return(false); } SeriesInfo reimport = null; if (aspects.ContainsKey(ReimportAspect.ASPECT_ID)) { reimport = new SeriesInfo(); reimport.FromMetadata(aspects); } IList <IDictionary <Guid, IList <MediaItemAspect> > > nfoLinkedAspects = new List <IDictionary <Guid, IList <MediaItemAspect> > >(); if (!await TryExtractSeriesActorsMetadataAsync(mediaItemAccessor, nfoLinkedAspects, reimport).ConfigureAwait(false)) { return(false); } List <PersonInfo> actors; if (!RelationshipExtractorUtils.TryCreateInfoFromLinkedAspects(nfoLinkedAspects, out actors)) { return(false); } actors = actors.Where(p => p != null && !string.IsNullOrEmpty(p.Name)).ToList(); if (actors.Count == 0) { return(false); } extractedLinkedAspects.Clear(); foreach (PersonInfo person in actors.Take(NfoSeriesMetadataExtractor.MaximumActorCount)) { if (person.SetLinkedMetadata() && person.LinkedAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { extractedLinkedAspects.Add(person.LinkedAspects); } } return(extractedLinkedAspects.Count > 0); }
/// <summary> /// Verifies if the series being reimported matches the series in the nfo file /// </summary> /// <param name="reader">Reader used read the series information from the nfo file</param> /// <param name="reimport">The series being reimported</param> /// <returns>Result of the verification</returns> protected bool VerifySeriesReimport(NfoSeriesReader reader, SeriesInfo reimport) { if (reimport == null) { return(true); } IDictionary <Guid, IList <MediaItemAspect> > aspectData = new Dictionary <Guid, IList <MediaItemAspect> >(); if (reader.TryWriteMetadata(aspectData)) { SeriesInfo info = new SeriesInfo(); 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) { SeriesInfo reimport = null; if (aspects.ContainsKey(ReimportAspect.ASPECT_ID)) { reimport = new SeriesInfo(); reimport.FromMetadata(aspects); } IList <IDictionary <Guid, IList <MediaItemAspect> > > nfoLinkedAspects = new List <IDictionary <Guid, IList <MediaItemAspect> > >(); if (!await TryExtractSeriesCharactersMetadataAsync(mediaItemAccessor, nfoLinkedAspects, 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 async Task <bool> TryExtractRelationshipsAsync(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > aspects, IList <IDictionary <Guid, IList <MediaItemAspect> > > extractedLinkedAspects) { SeriesInfo seriesInfo = new SeriesInfo(); if (!seriesInfo.FromMetadata(aspects)) { return(false); } if (RelationshipExtractorUtils.TryCreateInfoFromLinkedAspects(extractedLinkedAspects, out List <CompanyInfo> networks)) { seriesInfo.Networks = networks; } if (SeriesMetadataExtractor.IncludeTVNetworkDetails && !SeriesMetadataExtractor.SkipOnlineSearches) { await OnlineMatcherService.Instance.UpdateSeriesCompaniesAsync(seriesInfo, CompanyAspect.COMPANY_TV_NETWORK).ConfigureAwait(false); } foreach (CompanyInfo company in seriesInfo.Networks) { if (company.LinkedAspects != null) { company.SetLinkedMetadata(); } else { IDictionary <Guid, IList <MediaItemAspect> > companyAspects = new Dictionary <Guid, IList <MediaItemAspect> >(); if (company.SetMetadata(companyAspects) && companyAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { extractedLinkedAspects.Add(companyAspects); } } } return(extractedLinkedAspects.Count > 0); }
public async Task <bool> TryExtractRelationshipsAsync(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > aspects, IList <IDictionary <Guid, IList <MediaItemAspect> > > extractedLinkedAspects) { SeriesInfo seriesInfo = new SeriesInfo(); if (!seriesInfo.FromMetadata(aspects)) { return(false); } if (RelationshipExtractorUtils.TryCreateInfoFromLinkedAspects(extractedLinkedAspects, out List <PersonInfo> actors)) { seriesInfo.Actors = actors; } if (SeriesMetadataExtractor.IncludeActorDetails && !SeriesMetadataExtractor.SkipOnlineSearches) { await OnlineMatcherService.Instance.UpdateSeriesPersonsAsync(seriesInfo, PersonAspect.OCCUPATION_ACTOR).ConfigureAwait(false); } foreach (PersonInfo person in seriesInfo.Actors.Take(SeriesMetadataExtractor.MaximumActorCount)) { 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) { SeriesInfo seriesInfo = new SeriesInfo(); if (!seriesInfo.FromMetadata(aspects)) { return(false); } if (RelationshipExtractorUtils.TryCreateInfoFromLinkedAspects(extractedLinkedAspects, out List <CharacterInfo> characters)) { seriesInfo.Characters = characters; } if (SeriesMetadataExtractor.IncludeCharacterDetails && !SeriesMetadataExtractor.SkipOnlineSearches) { await OnlineMatcherService.Instance.UpdateSeriesCharactersAsync(seriesInfo, _category).ConfigureAwait(false); } foreach (CharacterInfo character in seriesInfo.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 bool TryExtractRelationships(IDictionary <Guid, IList <MediaItemAspect> > aspects, bool importOnly, out IList <RelationshipItem> extractedLinkedAspects) { extractedLinkedAspects = null; if (!SeriesMetadataExtractor.IncludeCharacterDetails) { return(false); } if (importOnly) { return(false); } SeriesInfo seriesInfo = new SeriesInfo(); if (!seriesInfo.FromMetadata(aspects)) { return(false); } if (CheckCacheContains(seriesInfo)) { return(false); } int count = 0; if (!SeriesMetadataExtractor.SkipOnlineSearches) { OnlineMatcherService.Instance.UpdateSeriesCharacters(seriesInfo, importOnly); count = seriesInfo.Characters.Where(p => p.HasExternalId).Count(); if (!seriesInfo.IsRefreshed) { seriesInfo.HasChanged = true; //Force save to update external Ids for metadata found by other MDEs } } else { count = seriesInfo.Characters.Where(p => !string.IsNullOrEmpty(p.Name)).Count(); } if (seriesInfo.Characters.Count == 0) { return(false); } if (BaseInfo.CountRelationships(aspects, LinkedRole) < count || (BaseInfo.CountRelationships(aspects, LinkedRole) == 0 && seriesInfo.Characters.Count > 0)) { seriesInfo.HasChanged = true; //Force save if no relationship exists } if (!seriesInfo.HasChanged) { return(false); } AddToCheckCache(seriesInfo); extractedLinkedAspects = new List <RelationshipItem>(); foreach (CharacterInfo character in seriesInfo.Characters) { character.AssignNameId(); character.HasChanged = seriesInfo.HasChanged; IDictionary <Guid, IList <MediaItemAspect> > characterAspects = new Dictionary <Guid, IList <MediaItemAspect> >(); character.SetMetadata(characterAspects); if (characterAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { Guid existingId; if (TryGetIdFromCache(character, out existingId)) { extractedLinkedAspects.Add(new RelationshipItem(characterAspects, existingId)); } else { extractedLinkedAspects.Add(new RelationshipItem(characterAspects, Guid.Empty)); } } } return(extractedLinkedAspects.Count > 0); }
/// <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); } }
public override void Update(MediaItem mediaItem) { base.Update(mediaItem); if (mediaItem == null) { return; } SeriesInfo seriesInfo = new SeriesInfo(); if (!seriesInfo.FromMetadata(mediaItem.Aspects)) { return; } Series = seriesInfo.SeriesName.Text ?? ""; StoryPlot = seriesInfo.Description.Text ?? ""; AvailableSeasons = ""; TotalSeasons = ""; AvailableEpisodes = ""; TotalEpisodes = ""; int?count; if (mediaItem.Aspects.ContainsKey(SeriesAspect.ASPECT_ID)) { if (MediaItemAspect.TryGetAttribute(mediaItem.Aspects, SeriesAspect.ATTR_AVAILABLE_SEASONS, out count)) { AvailableSeasons = count.Value.ToString(); } if (MediaItemAspect.TryGetAttribute(mediaItem.Aspects, SeriesAspect.ATTR_NUM_SEASONS, out count)) { TotalSeasons = count.Value.ToString(); } if (VirtualMediaHelper.ShowVirtualSeriesMedia) { Seasons = string.IsNullOrEmpty(TotalSeasons) ? AvailableSeasons : TotalSeasons; } else { Seasons = AvailableSeasons; } if (MediaItemAspect.TryGetAttribute(mediaItem.Aspects, SeriesAspect.ATTR_AVAILABLE_EPISODES, out count)) { AvailableEpisodes = count.Value.ToString(); } if (MediaItemAspect.TryGetAttribute(mediaItem.Aspects, SeriesAspect.ATTR_NUM_EPISODES, out count)) { TotalEpisodes = count.Value.ToString(); } if (VirtualMediaHelper.ShowVirtualSeriesMedia) { Episodes = string.IsNullOrEmpty(TotalEpisodes) ? AvailableEpisodes : TotalEpisodes; } else { Episodes = AvailableEpisodes; } string text; if (MediaItemAspect.TryGetAttribute(mediaItem.Aspects, SeriesAspect.ATTR_SERIES_NAME, out text)) { SimpleTitle = text; Series = text; } if (MediaItemAspect.TryGetAttribute(mediaItem.Aspects, SeriesAspect.ATTR_DESCRIPTION, out text)) { StoryPlot = text; } } FireChange(); }
public bool TryExtractRelationships(IDictionary <Guid, IList <MediaItemAspect> > aspects, bool importOnly, out IList <RelationshipItem> extractedLinkedAspects) { extractedLinkedAspects = null; if (importOnly) { return(false); } if (SeriesMetadataExtractor.OnlyLocalMedia) { return(false); } SeriesInfo seriesInfo = new SeriesInfo(); if (!seriesInfo.FromMetadata(aspects)) { return(false); } if (CheckCacheContains(seriesInfo)) { return(false); } if (!SeriesMetadataExtractor.SkipOnlineSearches) { OnlineMatcherService.Instance.UpdateSeries(seriesInfo, true, false); } if (seriesInfo.Episodes.Count == 0) { return(false); } if (BaseInfo.CountRelationships(aspects, LinkedRole) < seriesInfo.Episodes.Count) { seriesInfo.HasChanged = true; //Force save for new episodes } else { return(false); } if (!seriesInfo.HasChanged) { return(false); } AddToCheckCache(seriesInfo); extractedLinkedAspects = new List <RelationshipItem>(); for (int i = 0; i < seriesInfo.Episodes.Count; i++) { EpisodeInfo episodeInfo = seriesInfo.Episodes[i]; episodeInfo.SeriesNameId = seriesInfo.NameId; IDictionary <Guid, IList <MediaItemAspect> > episodeAspects = new Dictionary <Guid, IList <MediaItemAspect> >(); episodeInfo.SetMetadata(episodeAspects); MediaItemAspect.SetAttribute(episodeAspects, MediaAspect.ATTR_ISVIRTUAL, true); if (episodeAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { extractedLinkedAspects.Add(new RelationshipItem(episodeAspects, Guid.Empty)); } } return(extractedLinkedAspects.Count > 0); }
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); }