protected override async Task <bool> ExtractMetadataAsync(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData, bool forceQuickMode) { if (!CanExtract(lfsra, extractedAspectData)) { return(false); } if (extractedAspectData.ContainsKey(EpisodeAspect.ASPECT_ID)) { return(false); } // Handle series information EpisodeInfo episodeInfo = new EpisodeInfo(); using (var rec = new MCRecMetadataEditor(lfsra.LocalFileSystemPath)) { IDictionary tags = rec.GetAttributes(); episodeInfo = GetSeriesFromTags(tags); } if (episodeInfo.IsBaseInfoPresent) { if (!forceQuickMode) { await OnlineMatcherService.Instance.FindAndUpdateEpisodeAsync(episodeInfo).ConfigureAwait(false); } if (episodeInfo.IsBaseInfoPresent) { episodeInfo.SetMetadata(extractedAspectData); } } return(episodeInfo.IsBaseInfoPresent); }
public IDictionary <Guid, IList <MediaItemAspect> > GetBaseChildAspectsFromExistingAspects(IDictionary <Guid, IList <MediaItemAspect> > existingChildAspects, IDictionary <Guid, IList <MediaItemAspect> > existingParentAspects) { if (existingParentAspects.ContainsKey(SeriesAspect.ASPECT_ID)) { SeriesInfo series = new SeriesInfo(); series.FromMetadata(existingParentAspects); if (existingChildAspects.ContainsKey(SeasonAspect.ASPECT_ID)) { SeasonInfo season = new SeasonInfo(); season.FromMetadata(existingChildAspects); SeasonInfo basicSeason = series.CloneBasicInstance <SeasonInfo>(); basicSeason.SeasonNumber = season.SeasonNumber; IDictionary <Guid, IList <MediaItemAspect> > aspects = new Dictionary <Guid, IList <MediaItemAspect> >(); basicSeason.SetMetadata(aspects, true); return(aspects); } else if (existingChildAspects.ContainsKey(EpisodeAspect.ASPECT_ID)) { EpisodeInfo episode = new EpisodeInfo(); episode.FromMetadata(existingChildAspects); EpisodeInfo basicEpisode = series.CloneBasicInstance <EpisodeInfo>(); basicEpisode.SeasonNumber = episode.SeasonNumber; basicEpisode.EpisodeNumbers = episode.EpisodeNumbers.ToList(); IDictionary <Guid, IList <MediaItemAspect> > aspects = new Dictionary <Guid, IList <MediaItemAspect> >(); basicEpisode.SetMetadata(aspects, true); return(aspects); } } return(null); }
public override async Task <bool> TryExtractMetadataAsync(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData, bool forceQuickMode) { try { IResourceAccessor metaFileAccessor; if (!CanExtract(mediaItemAccessor, extractedAspectData, out metaFileAccessor)) { return(false); } if (!extractedAspectData.ContainsKey(VideoAspect.ASPECT_ID)) { return(false); //Ignore radio recordings } if (extractedAspectData.ContainsKey(EpisodeAspect.ASPECT_ID)) { return(false); } Tags tags; using (metaFileAccessor) { using (Stream metaStream = ((IFileSystemResourceAccessor)metaFileAccessor).OpenRead()) tags = (Tags)GetTagsXmlSerializer().Deserialize(metaStream); } // Handle series information EpisodeInfo episodeInfo = GetSeriesFromTags(tags); if (episodeInfo.IsBaseInfoPresent) { if (!forceQuickMode) { await OnlineMatcherService.Instance.FindAndUpdateEpisodeAsync(episodeInfo, MEDIA_CATEGORY_NAME_SERIES).ConfigureAwait(false); } if (episodeInfo.IsBaseInfoPresent) { episodeInfo.SetMetadata(extractedAspectData); } } return(episodeInfo.IsBaseInfoPresent); } 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("Tve3RecordingSeriesMetadataExtractor: Exception reading resource '{0}' (Text: '{1}')", mediaItemAccessor.CanonicalLocalResourcePath, e.Message); } return(false); }
public override bool TryExtractMetadata(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData, bool importOnly, bool forceQuickMode) { try { IResourceAccessor metaFileAccessor; if (!CanExtract(mediaItemAccessor, extractedAspectData, out metaFileAccessor)) { return(false); } if (extractedAspectData.ContainsKey(EpisodeAspect.ASPECT_ID)) { return(false); } // Handle series information Argus.Recording recording; using (metaFileAccessor) { using (Stream metaStream = ((IFileSystemResourceAccessor)metaFileAccessor).OpenRead()) recording = (Argus.Recording)GetTagsXmlSerializer().Deserialize(metaStream); } EpisodeInfo episodeInfo = GetSeriesFromTags(recording); if (episodeInfo.IsBaseInfoPresent) { if (!forceQuickMode) { OnlineMatcherService.Instance.FindAndUpdateEpisode(episodeInfo, importOnly); } if (episodeInfo.IsBaseInfoPresent) { episodeInfo.SetMetadata(extractedAspectData); } } return(episodeInfo.IsBaseInfoPresent); } 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("ArgusRecordingSeriesMetadataExtractor: Exception reading resource '{0}' (Text: '{1}')", mediaItemAccessor.CanonicalLocalResourcePath, e.Message); } return(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); } }
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); } }
public bool TryExtractRelationships(IDictionary <Guid, IList <MediaItemAspect> > aspects, bool importOnly, out IList <RelationshipItem> extractedLinkedAspects) { extractedLinkedAspects = null; if (importOnly) { return(false); } if (SeriesMetadataExtractor.OnlyLocalMedia) { return(false); } SeriesInfo seriesInfo = new SeriesInfo(); if (!seriesInfo.FromMetadata(aspects)) { return(false); } if (CheckCacheContains(seriesInfo)) { return(false); } if (!SeriesMetadataExtractor.SkipOnlineSearches) { OnlineMatcherService.Instance.UpdateSeries(seriesInfo, true, false); } if (seriesInfo.Episodes.Count == 0) { return(false); } if (BaseInfo.CountRelationships(aspects, LinkedRole) < seriesInfo.Episodes.Count) { seriesInfo.HasChanged = true; //Force save for new episodes } else { return(false); } if (!seriesInfo.HasChanged) { return(false); } AddToCheckCache(seriesInfo); extractedLinkedAspects = new List <RelationshipItem>(); for (int i = 0; i < seriesInfo.Episodes.Count; i++) { EpisodeInfo episodeInfo = seriesInfo.Episodes[i]; episodeInfo.SeriesNameId = seriesInfo.NameId; IDictionary <Guid, IList <MediaItemAspect> > episodeAspects = new Dictionary <Guid, IList <MediaItemAspect> >(); episodeInfo.SetMetadata(episodeAspects); MediaItemAspect.SetAttribute(episodeAspects, MediaAspect.ATTR_ISVIRTUAL, true); if (episodeAspects.ContainsKey(ExternalIdentifierAspect.ASPECT_ID)) { extractedLinkedAspects.Add(new RelationshipItem(episodeAspects, Guid.Empty)); } } return(extractedLinkedAspects.Count > 0); }
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); }
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); }
/// <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="importOnly">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 importOnly, 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); try { _debugLogger.Info("[#{0}]: Start extracting metadata for resource '{1}' (importOnly: {2}, forceQuickMode: {3})", miNumber, mediaItemAccessor, importOnly, forceQuickMode); // 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); } // 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); } // 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; if (TryGetEpisodeNfoSResourceAccessor(miNumber, mediaItemAccessor as IFileSystemResourceAccessor, out episodeNfoFsra)) { // 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, importOnly, forceQuickMode, _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, importOnly, forceQuickMode, _httpClient, _settings); using (seriesNfoFsra) { if (await seriesNfoReader.TryReadMetadataAsync(seriesNfoFsra).ConfigureAwait(false)) { Stubs.SeriesStub series = seriesNfoReader.GetSeriesStubs().First(); 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); } INfoRelationshipExtractor.StoreSeries(extractedAspectData, series); } else { _debugLogger.Warn("[#{0}]: No valid metadata found in series nfo-file", miNumber); } } } _debugLogger.Info("[#{0}]: Successfully finished extracting metadata", miNumber); 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); } }