public static bool TryMatchImdbId(string folderOrFileName, out string imdbId)
    {
      string extensionLower = StringUtils.TrimToEmpty(Path.GetExtension(folderOrFileName)).ToLower();
      if (!MatroskaConsts.MATROSKA_VIDEO_EXTENSIONS.Contains(extensionLower))
      {
        imdbId = null;
        return false;
      }

      MatroskaInfoReader mkvReader = new MatroskaInfoReader(folderOrFileName);
      // Add keys to be extracted to tags dictionary, matching results will returned as value
      Dictionary<string, IList<string>> tagsToExtract = MatroskaConsts.DefaultTags;
      mkvReader.ReadTags(tagsToExtract);

      if (tagsToExtract[MatroskaConsts.TAG_MOVIE_IMDB_ID] != null)
      {
        foreach (string candidate in tagsToExtract[MatroskaConsts.TAG_MOVIE_IMDB_ID])
        {
          if (ImdbIdMatcher.TryMatchImdbId(candidate, out imdbId))
            return true;
        }
      }

      imdbId = null;
      return false;
    }
    public static bool TryMatchTmdbId(ILocalFsResourceAccessor folderOrFileLfsra, out int tmdbId)
    {
      // Calling EnsureLocalFileSystemAccess not necessary; only string operation
      string extensionLower = StringUtils.TrimToEmpty(Path.GetExtension(folderOrFileLfsra.LocalFileSystemPath)).ToLower();
      if (!MatroskaConsts.MATROSKA_VIDEO_EXTENSIONS.Contains(extensionLower))
      {
        tmdbId = 0;
        return false;
      }

      MatroskaInfoReader mkvReader = new MatroskaInfoReader(folderOrFileLfsra);
      // Add keys to be extracted to tags dictionary, matching results will returned as value
      Dictionary<string, IList<string>> tagsToExtract = MatroskaConsts.DefaultTags;
      mkvReader.ReadTags(tagsToExtract);

      if (tagsToExtract[MatroskaConsts.TAG_MOVIE_TMDB_ID] != null)
      {
        foreach (string candidate in tagsToExtract[MatroskaConsts.TAG_MOVIE_TMDB_ID])
        {
          if (int.TryParse(candidate, out tmdbId))
            return true;
        }
      }

      tmdbId = 0;
      return false;
    }
    /// <summary>
    /// Tries to match series by reading matroska tags from <paramref name="folderOrFileLfsra"/>.
    /// </summary>
    /// <param name="folderOrFileLfsra"><see cref="ILocalFsResourceAccessor"/> to file or folder</param>
    /// <param name="seriesInfo">Returns the parsed SeriesInfo</param>
    /// <param name="extractedAspectData">Dictionary containing a mapping of media item aspect ids to
    /// already present media item aspects, this metadata extractor should edit. If a media item aspect is not present
    /// in this dictionary but found by this metadata extractor, it will add it to the dictionary.</param>
    /// <returns><c>true</c> if successful.</returns>
    public bool MatchSeries(ILocalFsResourceAccessor folderOrFileLfsra, out SeriesInfo seriesInfo, ref IDictionary<Guid, MediaItemAspect> extractedAspectData)
    {
      // Calling EnsureLocalFileSystemAccess not necessary; only string operation
      string extensionLower = StringUtils.TrimToEmpty(Path.GetExtension(folderOrFileLfsra.LocalFileSystemPath)).ToLower();

      if (!MatroskaConsts.MATROSKA_VIDEO_EXTENSIONS.Contains(extensionLower))
      {
        seriesInfo = null;
        return false;
      }

      MatroskaInfoReader mkvReader = new MatroskaInfoReader(folderOrFileLfsra);
      // Add keys to be extracted to tags dictionary, matching results will returned as value
      Dictionary<string, IList<string>> tagsToExtract = MatroskaConsts.DefaultTags;
      mkvReader.ReadTags(tagsToExtract);

      string title = string.Empty;
      IList<string> tags = tagsToExtract[MatroskaConsts.TAG_SIMPLE_TITLE];
      if (tags != null)
        title = tags.FirstOrDefault();

      if (!string.IsNullOrEmpty(title))
        MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, title);

      string yearCandidate = null;
      tags = tagsToExtract[MatroskaConsts.TAG_EPISODE_YEAR] ?? tagsToExtract[MatroskaConsts.TAG_SEASON_YEAR];
      if (tags != null)
        yearCandidate = (tags.FirstOrDefault() ?? string.Empty).Substring(0, 4);

      int year;
      if (int.TryParse(yearCandidate, out year))
        MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, new DateTime(year, 1, 1));

      tags = tagsToExtract[MatroskaConsts.TAG_EPISODE_SUMMARY];
      string plot = tags != null ? tags.FirstOrDefault() : string.Empty;
      if (!string.IsNullOrEmpty(plot))
        MediaItemAspect.SetAttribute(extractedAspectData, VideoAspect.ATTR_STORYPLOT, plot);

      // Series and episode handling. Prefer information from tags.
      seriesInfo = GetSeriesFromTags(tagsToExtract);

      return true;
    }
    protected bool ExtractSeriesData(string localFsResourcePath, IDictionary<Guid, MediaItemAspect> extractedAspectData)
    {
      SeriesInfo seriesInfo = null;

      string extensionUpper = StringUtils.TrimToEmpty(Path.GetExtension(localFsResourcePath)).ToUpper();

      // Try to get extended information out of matroska files)
      if (extensionUpper == ".MKV" || extensionUpper == ".MK3D")
      {
        MatroskaInfoReader mkvReader = new MatroskaInfoReader(localFsResourcePath);
        // Add keys to be extracted to tags dictionary, matching results will returned as value
        Dictionary<string, IList<string>> tagsToExtract = MatroskaConsts.DefaultTags;
        mkvReader.ReadTags(tagsToExtract);

        string title = string.Empty;
        IList<string> tags = tagsToExtract[MatroskaConsts.TAG_SIMPLE_TITLE];
        if (tags != null)
          title = tags.FirstOrDefault();

        if (!string.IsNullOrEmpty(title))
          MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, title);

        string yearCandidate = null;
        tags = tagsToExtract[MatroskaConsts.TAG_EPISODE_YEAR] ?? tagsToExtract[MatroskaConsts.TAG_SEASON_YEAR];
        if (tags != null)
          yearCandidate = (tags.FirstOrDefault() ?? string.Empty).Substring(0, 4);

        int year;
        if (int.TryParse(yearCandidate, out year))
          MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, new DateTime(year, 1, 1));

        tags = tagsToExtract[MatroskaConsts.TAG_EPISODE_SUMMARY];
        string plot = tags != null ? tags.FirstOrDefault() : string.Empty;
        if (!string.IsNullOrEmpty(plot))
          MediaItemAspect.SetAttribute(extractedAspectData, VideoAspect.ATTR_STORYPLOT, plot);

        // Series and episode handling. Prefer information from tags.
        seriesInfo = GetSeriesFromTags(tagsToExtract);
      }

      // If now information from mkv were found, try name matching
      if (seriesInfo == null || !seriesInfo.IsCompleteMatch)
      {
        // Try to match series from folder and file namings
        SeriesMatcher seriesMatcher = new SeriesMatcher();
        seriesMatcher.MatchSeries(localFsResourcePath, out seriesInfo);
      }

      // Lookup online information (incl. fanart)
      if (seriesInfo != null && seriesInfo.IsCompleteMatch)
      {
        SeriesTvDbMatcher.Instance.FindAndUpdateSeries(seriesInfo);
        seriesInfo.SetMetadata(extractedAspectData);
      }
      return (seriesInfo != null && seriesInfo.IsCompleteMatch);
    }
    public bool TryGetFanArt(string mediaType, string fanArtType, string name, int maxWidth, int maxHeight, bool singleRandom, out IList<FanArtImage> result)
    {
      result = null;
      Guid mediaItemId;
      if (!Guid.TryParse(name, out mediaItemId))
        return false;

      IMediaLibrary mediaLibrary = ServiceRegistration.Get<IMediaLibrary>(false);
      if (mediaLibrary == null)
        return false;

      IFilter filter = new MediaItemIdFilter(mediaItemId);
      IList<MediaItem> items = mediaLibrary.Search(new MediaItemQuery(NECESSARY_MIAS, filter), false);
      if (items == null || items.Count == 0)
        return false;

      MediaItem mediaItem = items.First();

      string fileSystemPath = string.Empty;
      IList<string> patterns = new List<string>();
      switch (fanArtType)
      {
        case FanArtTypes.Banner:
          patterns.Add("banner.");
          break;
        case FanArtTypes.ClearArt:
          patterns.Add("clearart.");
          break;
        case FanArtTypes.Poster:
        case FanArtTypes.Thumbnail:
          patterns.Add("cover.");
          patterns.Add("poster.");
          patterns.Add("folder.");
          break;
        case FanArtTypes.FanArt:
          patterns.Add("backdrop.");
          patterns.Add("fanart.");
          break;
        default:
          return false;
      }
      // File based access
      try
      {
        var resourceLocator = mediaItem.GetResourceLocator();
        using (var accessor = resourceLocator.CreateAccessor())
        {
          ILocalFsResourceAccessor fsra = accessor as ILocalFsResourceAccessor;
          if (fsra != null)
          {
            var ext = Path.GetExtension(fsra.LocalFileSystemPath);
            if (!SUPPORTED_EXTENSIONS.Contains(ext))
              return false;

            MatroskaInfoReader mkvReader = new MatroskaInfoReader(fsra);
            byte[] binaryData = null;
            if (patterns.Any(pattern => mkvReader.GetAttachmentByName(pattern, out binaryData)))
            {
              result = new List<FanArtImage> { new FanArtImage(name, binaryData) };
              return true;
            }
          }
        }
      }
      catch (Exception ex)
      {
        ServiceRegistration.Get<ILogger>().Warn("MkvAttachmentsProvider: Exception while reading mkv attachment of type '{0}' from '{1}'", ex, fanArtType, fileSystemPath);
      }
      return false;
    }
    protected void ExtractMatroskaTags(ILocalFsResourceAccessor lfsra, IDictionary<Guid, MediaItemAspect> extractedAspectData, bool forceQuickMode)
    {
      // Calling EnsureLocalFileSystemAccess not necessary; only string operation
      string extensionLower = StringUtils.TrimToEmpty(Path.GetExtension(lfsra.LocalFileSystemPath)).ToLower();
      if (!MatroskaConsts.MATROSKA_VIDEO_EXTENSIONS.Contains(extensionLower))
        return;

      // Try to get extended information out of matroska files)
      MatroskaInfoReader mkvReader = new MatroskaInfoReader(lfsra);
      // Add keys to be extracted to tags dictionary, matching results will returned as value
      Dictionary<string, IList<string>> tagsToExtract = MatroskaConsts.DefaultTags;
      mkvReader.ReadTags(tagsToExtract);

      // Read title
      string title = string.Empty;
      IList<string> tags = tagsToExtract[MatroskaConsts.TAG_SIMPLE_TITLE];
      if (tags != null)
        title = tags.FirstOrDefault();
      if (!string.IsNullOrEmpty(title))
        MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, title);

      // Read release date
      int year;
      string yearCandidate = null;
      tags = tagsToExtract[MatroskaConsts.TAG_EPISODE_YEAR] ?? tagsToExtract[MatroskaConsts.TAG_SEASON_YEAR];
      if (tags != null)
        yearCandidate = (tags.FirstOrDefault() ?? string.Empty).Substring(0, 4);

      if (int.TryParse(yearCandidate, out year))
        MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, new DateTime(year, 1, 1));

      // Read plot
      tags = tagsToExtract[MatroskaConsts.TAG_EPISODE_SUMMARY];
      string plot = tags != null ? tags.FirstOrDefault() : string.Empty;
      if (!string.IsNullOrEmpty(plot))
        MediaItemAspect.SetAttribute(extractedAspectData, VideoAspect.ATTR_STORYPLOT, plot);

      // Read genre
      tags = tagsToExtract[MatroskaConsts.TAG_SERIES_GENRE];
      if (tags != null)
        MediaItemAspect.SetCollectionAttribute(extractedAspectData, VideoAspect.ATTR_GENRES, tags);

      // Read actors
      IEnumerable<string> actors;
      // Combine series actors and episode actors if both are available
      var tagSeriesActors = tagsToExtract[MatroskaConsts.TAG_SERIES_ACTORS];
      var tagActors = tagsToExtract[MatroskaConsts.TAG_ACTORS];
      if (tagSeriesActors != null && tagActors != null)
        actors = tagSeriesActors.Union(tagActors);
      else
        actors = tagSeriesActors ?? tagActors;

      if (actors != null)
        MediaItemAspect.SetCollectionAttribute(extractedAspectData, VideoAspect.ATTR_ACTORS, actors);

      tags = tagsToExtract[MatroskaConsts.TAG_DIRECTORS];
      if (tags != null)
        MediaItemAspect.SetCollectionAttribute(extractedAspectData, VideoAspect.ATTR_DIRECTORS, tags);

      tags = tagsToExtract[MatroskaConsts.TAG_WRITTEN_BY];
      if (tags != null)
        MediaItemAspect.SetCollectionAttribute(extractedAspectData, VideoAspect.ATTR_WRITERS, tags);
    }
    protected void ExtractMatroskaTags(string localFsResourcePath, IDictionary<Guid, MediaItemAspect> extractedAspectData, bool forceQuickMode)
    {
      string extensionUpper = StringUtils.TrimToEmpty(Path.GetExtension(localFsResourcePath)).ToUpper();

      // Try to get extended information out of matroska files)
      if (extensionUpper == ".MKV" || extensionUpper == ".MK3D")
      {
        MatroskaInfoReader mkvReader = new MatroskaInfoReader(localFsResourcePath);
        // Add keys to be extracted to tags dictionary, matching results will returned as value
        Dictionary<string, IList<string>> tagsToExtract = MatroskaConsts.DefaultTags;
        mkvReader.ReadTags(tagsToExtract);

        string title = string.Empty;
        IList<string> tags = tagsToExtract[MatroskaConsts.TAG_SIMPLE_TITLE];
        if (tags != null)
          title = tags.FirstOrDefault();

        int year;
        if (!string.IsNullOrEmpty(title))
          MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, title);

        string yearCandidate = null;
        tags = tagsToExtract[MatroskaConsts.TAG_EPISODE_YEAR] ?? tagsToExtract[MatroskaConsts.TAG_SEASON_YEAR];
        if (tags != null)
          yearCandidate = (tags.FirstOrDefault() ?? string.Empty).Substring(0, 4);

        if (int.TryParse(yearCandidate, out year))
          MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, new DateTime(year, 1, 1));

        tags = tagsToExtract[MatroskaConsts.TAG_SERIES_GENRE];
        if (tags != null)
          MediaItemAspect.SetCollectionAttribute(extractedAspectData, VideoAspect.ATTR_GENRES, tags);

        IEnumerable<string> actors;
        // Combine series actors and episode actors if both are available
        var tagSeriesActors = tagsToExtract[MatroskaConsts.TAG_SERIES_ACTORS];
        var tagActors = tagsToExtract[MatroskaConsts.TAG_ACTORS];
        if (tagSeriesActors != null && tagActors != null)
          actors = tagSeriesActors.Union(tagActors);
        else
          actors = tagSeriesActors ?? tagActors;

        if (actors != null)
          MediaItemAspect.SetCollectionAttribute(extractedAspectData, VideoAspect.ATTR_ACTORS, actors);
      }

      // Movie handling
      //if (!string.IsNullOrEmpty(title) && !forceQuickMode)
      //{
      //  // TODO: online information can overwrite information read out of mkv tags, how to handle this?
      //  MovieInfo movieInfo = new MovieInfo
      //  {
      //    MovieName = title,
      //    Year = year
      //  };
      //  if (MovieTheMovieDbMatcher.Instance.FindAndUpdateMovie(movieInfo))
      //    movieInfo.SetMetadata(extractedAspectData);
      //}
    }