Esempio n. 1
0
        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);
        }
Esempio n. 3
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);
            }

            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);
        }
Esempio n. 4
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);
        }
Esempio n. 10
0
        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));
        }
Esempio n. 11
0
        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);
        }
Esempio n. 12
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);
                }
            }
        }
Esempio n. 15
0
        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);
                        }
                    }
                }
            }
        }