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; }
protected ILocalFsResourceAccessor _underlayingResource = null; // Only set if the path points to a file system resource - not a server #endregion public NetworkNeighborhoodResourceAccessor(NetworkNeighborhoodResourceProvider parent, string path) { _parent = parent; _path = path; if (IsServerPath(path)) return; IResourceAccessor ra; if (!LocalFsResourceProvider.Instance.TryCreateResourceAccessor("/" + path, out ra)) throw new IllegalCallException("Unable to access resource '{0}'", path); _underlayingResource = (ILocalFsResourceAccessor) ra; }
protected ILocalFsResourceAccessor _underlayingResource = null; // Only set if the path points to a file system resource - not a server or root #endregion #region Ctor public NetworkNeighborhoodResourceAccessor(NetworkNeighborhoodResourceProvider parent, string path) { _parent = parent; _path = path; if (IsRootPath(path ) || IsServerPath(path)) return; IResourceAccessor ra; using (ServiceRegistration.Get<IImpersonationService>().CheckImpersonationFor(CanonicalLocalResourcePath)) if (LocalFsResourceProvider.Instance.TryCreateResourceAccessor("/" + path, out ra)) _underlayingResource = (ILocalFsResourceAccessor)ra; }
public NetworkNeighborhoodResourceAccessor(NetworkNeighborhoodResourceProvider parent, string path) { _parent = parent; _path = path; if (IsServerPath(path)) return; _impersonationContext = ImpersonateUser(null); IResourceAccessor ra; if (LocalFsResourceProvider.Instance.TryCreateResourceAccessor("/" + path, out ra)) _underlayingResource = (ILocalFsResourceAccessor) ra; }
/// <summary> /// Creates a new <see cref="LocalFsResourceAccessor"/> instance. The given <paramref name="mediaItemAccessor"/> will be either directly used or /// given over to the <see cref="StreamedResourceToLocalFsAccessBridge"/>. The caller must call <see cref="Dispose"/> on the created <see cref="LocalFsResourceAccessorHelper"/> /// instance but must not dispose the given <paramref name="mediaItemAccessor"/>. /// </summary> /// <param name="mediaItemAccessor">IResourceAccessor.</param> public LocalFsResourceAccessorHelper(IResourceAccessor mediaItemAccessor) { _localFsra = mediaItemAccessor as ILocalFsResourceAccessor; _disposeLfsra = null; if (_localFsra != null) return; IFileSystemResourceAccessor localFsra = (IFileSystemResourceAccessor) mediaItemAccessor.Clone(); try { _localFsra = StreamedResourceToLocalFsAccessBridge.GetLocalFsResourceAccessor(localFsra); _disposeLfsra = _localFsra; } catch (Exception) { localFsra.Dispose(); throw; } }
/// <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; }
/// <summary> /// Executes an external program and ensures that it has access to the respective resource /// </summary> /// <param name="lfsra"><see cref="ILocalFsResourceAccessor"/> to which the external program needs access to</param> /// <param name="executable">External program to execute</param> /// <param name="arguments">Arguments for the external program</param> /// <param name="priorityClass">Process priority</param> /// <param name="maxWaitMs">Maximum time to wait for completion</param> /// <returns>A <see cref="Task"/> representing the result of executing the external program</returns> /// <remarks> /// This is a convenience method that enables executing an external procgram directly on the <see cref="ILocalFsResourceAccessor"/> /// interface to which the external program needs access. The purpose of an <see cref="ILocalFsResourceAccessor"/> is providing /// access to a resource - not executing programs which is why this method is implemented as an extension method instead of /// a method directly on the interface. /// </remarks> public static Task <ProcessExecutionResult> ExecuteWithResourceAccessAsync(this ILocalFsResourceAccessor lfsra, string executable, string arguments, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = ProcessUtils.DEFAULT_TIMEOUT) { return(ServiceRegistration.Get <IImpersonationService>().ExecuteWithResourceAccessAsync(lfsra.CanonicalLocalResourcePath, executable, arguments, priorityClass, maxWaitMs)); }
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); } }
public bool TryGetFanArt(string mediaType, string fanArtType, string name, int maxWidth, int maxHeight, bool singleRandom, out IList <FanArtImage> result) { result = null; Guid mediaItemId; if (mediaType != FanArtMediaTypes.Album && mediaType != FanArtMediaTypes.Audio) { return(false); } if (!Guid.TryParse(name, out mediaItemId)) { return(false); } IMediaLibrary mediaLibrary = ServiceRegistration.Get <IMediaLibrary>(false); if (mediaLibrary == null) { return(false); } IFilter filter = null; if (mediaType == FanArtMediaTypes.Album) { filter = new RelationshipFilter(AudioAspect.ROLE_TRACK, AudioAlbumAspect.ROLE_ALBUM, mediaItemId); } else if (mediaType == FanArtMediaTypes.Audio) { filter = new MediaItemIdFilter(mediaItemId); } MediaItemQuery mediaQuery = new MediaItemQuery(NECESSARY_MIAS, filter); mediaQuery.Limit = 1; IList <MediaItem> items = mediaLibrary.Search(mediaQuery, false, null, true); if (items == null || items.Count == 0) { return(false); } MediaItem mediaItem = items.First(); // Virtual resources won't have any local fanart if (mediaItem.IsVirtual) { return(false); } var resourceLocator = mediaItem.GetResourceLocator(); string fileSystemPath = string.Empty; IList <PictureType> patterns = new List <PictureType>(); switch (fanArtType) { case FanArtTypes.Undefined: case FanArtTypes.Poster: case FanArtTypes.Cover: case FanArtTypes.Thumbnail: patterns.Add(PictureType.FrontCover); patterns.Add(PictureType.Other); break; default: return(false); } // File based access try { using (var accessor = resourceLocator?.CreateAccessor()) { ILocalFsResourceAccessor fsra = accessor as ILocalFsResourceAccessor; if (fsra != null) { fileSystemPath = fsra.LocalFileSystemPath; var ext = Path.GetExtension(fsra.LocalFileSystemPath); if (!SUPPORTED_EXTENSIONS.Contains(ext)) { return(false); } ByteVector.UseBrokenLatin1Behavior = true; // Otherwise we have problems retrieving non-latin1 chars using (var tag = TagLib.File.Create(fsra.LocalFileSystemPath)) { IPicture[] pics = tag.Tag.Pictures; if (pics.Length > 0) { foreach (var pattern in patterns) { var picTag = pics.FirstOrDefault(p => p.Type == pattern); if (picTag != null) { result = new List <FanArtImage> { new FanArtImage(name, picTag.Data.Data) }; return(true); } } //If no matching images found, use first images for thumbnails if (fanArtType == FanArtTypes.Thumbnail) { result = new List <FanArtImage> { new FanArtImage(name, pics[0].Data.Data) }; return(true); } } } } } } catch (Exception ex) { ServiceRegistration.Get <ILogger>().Warn("AudioTagProvider: Exception while reading tag of type '{0}' from '{1}'", ex, fanArtType, fileSystemPath); } return(false); }
private bool CollectionFolderHasFanArt(ILocalFsResourceAccessor lfsra, out string collectionName) { collectionName = null; // File based access try { using (lfsra.EnsureLocalFileSystemAccess()) { string collectionMediaItemDirectoryPath; if (Directory.GetParent(lfsra.LocalFileSystemPath) != null && Directory.GetParent(Directory.GetParent(lfsra.LocalFileSystemPath).FullName) != null) { DirectoryInfo dir = Directory.GetParent(Directory.GetParent(lfsra.LocalFileSystemPath).FullName); collectionMediaItemDirectoryPath = dir.FullName; collectionName = dir.Name; } else { return(false); } var potentialFanArtFiles = GetPotentialFanArtFiles(collectionMediaItemDirectoryPath); if ((from potentialFanArtFile in potentialFanArtFiles let potentialFanArtFileNameWithoutExtension = Path.GetFileNameWithoutExtension(potentialFanArtFile.ToString()).ToLowerInvariant() where potentialFanArtFileNameWithoutExtension == "poster" || potentialFanArtFileNameWithoutExtension == "folder" || potentialFanArtFileNameWithoutExtension == "movieset-poster" select potentialFanArtFile).Count() > 0) { return(true); } if ((from potentialFanArtFile in potentialFanArtFiles let potentialFanArtFileNameWithoutExtension = Path.GetFileNameWithoutExtension(potentialFanArtFile.ToString()).ToLowerInvariant() where potentialFanArtFileNameWithoutExtension == "banner" || potentialFanArtFileNameWithoutExtension == "movieset-banner" select potentialFanArtFile).Count() > 0) { return(true); } if ((from potentialFanArtFile in potentialFanArtFiles let potentialFanArtFileNameWithoutExtension = Path.GetFileNameWithoutExtension(potentialFanArtFile.ToString()).ToLowerInvariant() where potentialFanArtFileNameWithoutExtension == "backdrop" || potentialFanArtFileNameWithoutExtension == "fanart" || potentialFanArtFileNameWithoutExtension == "movieset-fanart" select potentialFanArtFile).Count() > 0) { return(true); } string fanArtFolder = Path.Combine(collectionMediaItemDirectoryPath, "ExtraFanArt"); if (Directory.Exists(fanArtFolder)) { if (GetPotentialFanArtFiles(fanArtFolder).Count() > 0) { return(true); } } } } catch { } return(false); }
private bool ExtractThumbnail(ILocalFsResourceAccessor lfsra, IDictionary<Guid, MediaItemAspect> extractedAspectData) { // We can only work on files and make sure this file was detected by a lower MDE before (title is set then). // VideoAspect must be present to be sure it is actually a video resource. if (!lfsra.IsFile || !extractedAspectData.ContainsKey(VideoAspect.ASPECT_ID)) return false; byte[] thumb; // We only want to create missing thumbnails here, so check for existing ones first if (MediaItemAspect.TryGetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, out thumb) && thumb != null) return true; // Check for a reasonable time offset long defaultVideoOffset = 720; long videoDuration; if (MediaItemAspect.TryGetAttribute(extractedAspectData, VideoAspect.ATTR_DURATION, out videoDuration)) { if (defaultVideoOffset > videoDuration * 1 / 3) defaultVideoOffset = videoDuration * 1 / 3; } string downscale = ",scale=iw/2:-1"; // Reduces the video frame size to a half of original int videoWidth; if (MediaItemAspect.TryGetAttribute(extractedAspectData, VideoAspect.ATTR_WIDTH, out videoWidth)) { // Don't downscale SD video frames, quality is already quite low. if (videoWidth > 0 && videoWidth <= 720) downscale = ""; } // ToDo: Move creation of temp file names to FileUtils class string tempFileName = Path.GetTempPath() + Guid.NewGuid() + ".jpg"; string executable = FileUtils.BuildAssemblyRelativePath("ffmpeg.exe"); string arguments = string.Format("-ss {0} -i \"{1}\" -vframes 1 -an -dn -vf \"yadif='mode=send_frame:parity=auto:deint=all',scale=iw*sar:ih,setsar=1/1{3}\" -y \"{2}\"", defaultVideoOffset, // Calling EnsureLocalFileSystemAccess not necessary; access for external process ensured by ExecuteWithResourceAccess lfsra.LocalFileSystemPath, tempFileName, downscale); try { bool success; lock (FFMPEG_THROTTLE_LOCK) success = lfsra.ExecuteWithResourceAccessAsync(executable, arguments, ProcessPriorityClass.Idle, PROCESS_TIMEOUT_MS).Result.Success; if (success && File.Exists(tempFileName)) { var binary = FileUtils.ReadFile(tempFileName); MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, binary); // Calling EnsureLocalFileSystemAccess not necessary; only string operation ServiceRegistration.Get<ILogger>().Info("VideoThumbnailer: Successfully created thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } else // Calling EnsureLocalFileSystemAccess not necessary; only string operation ServiceRegistration.Get<ILogger>().Warn("VideoThumbnailer: Failed to create thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } catch (AggregateException ae) { ae.Handle(e => { if (e is TaskCanceledException) { ServiceRegistration.Get<ILogger>().Warn("VideoThumbnailer.ExtractThumbnail: External process aborted due to timeout: Executable='{0}', Arguments='{1}', Timeout='{2}'", executable, arguments, PROCESS_TIMEOUT_MS); return true; } return false; }); } finally { if (File.Exists(tempFileName)) File.Delete(tempFileName); } return true; }
public bool TryCreateLocalFsAccessor(out ILocalFsResourceAccessor localFsResourceAccessor) { IResourceAccessor accessor = CreateAccessor(); IFileSystemResourceAccessor fsra = accessor as IFileSystemResourceAccessor; if (fsra == null) { accessor.Dispose(); localFsResourceAccessor = null; return false; } try { localFsResourceAccessor = StreamedResourceToLocalFsAccessBridge.StreamedResourceToLocalFsAccessBridge.GetLocalFsResourceAccessor(fsra); return true; } catch { accessor.Dispose(); throw; } }
protected void ExtractThumbnailData(ILocalFsResourceAccessor lfsra, IDictionary<Guid, MediaItemAspect> extractedAspectData, bool forceQuickMode) { // In quick mode only allow thumbs taken from cache. bool cachedOnly = forceQuickMode; // Thumbnail extraction IThumbnailGenerator generator = ServiceRegistration.Get<IThumbnailGenerator>(); byte[] thumbData; ImageType imageType; using (lfsra.EnsureLocalFileSystemAccess()) if (generator.GetThumbnail(lfsra.LocalFileSystemPath, 256, 256, cachedOnly, out thumbData, out imageType)) MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, thumbData); }
private async Task <bool> ExtractThumbnailAsync(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData) { // We can only work on files and make sure this file was detected by a lower MDE before (title is set then). // VideoAspect must be present to be sure it is actually a video resource. if (!lfsra.IsFile || !extractedAspectData.ContainsKey(VideoStreamAspect.ASPECT_ID)) { return(false); } //ServiceRegistration.Get<ILogger>().Info("VideoThumbnailer: Evaluate {0}", lfsra.ResourceName); bool isPrimaryResource = false; IList <MultipleMediaItemAspect> resourceAspects; if (MediaItemAspect.TryGetAspects(extractedAspectData, ProviderResourceAspect.Metadata, out resourceAspects)) { foreach (MultipleMediaItemAspect pra in resourceAspects) { string accessorPath = (string)pra.GetAttributeValue(ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH); ResourcePath resourcePath = ResourcePath.Deserialize(accessorPath); if (resourcePath.Equals(lfsra.CanonicalLocalResourcePath)) { if (pra.GetAttributeValue <int?>(ProviderResourceAspect.ATTR_TYPE) == ProviderResourceAspect.TYPE_PRIMARY) { isPrimaryResource = true; break; } } } } if (!isPrimaryResource) //Ignore subtitles { return(false); } // Check for a reasonable time offset long defaultVideoOffset = 720; long videoDuration; string downscale = ",scale='min(256,iw)':-1"; // 256 is max size of large thumbnail aspect IList <MultipleMediaItemAspect> videoAspects; if (MediaItemAspect.TryGetAspects(extractedAspectData, VideoStreamAspect.Metadata, out videoAspects)) { if ((videoDuration = videoAspects[0].GetAttributeValue <long>(VideoStreamAspect.ATTR_DURATION)) > 0) { if (defaultVideoOffset > videoDuration * 1 / 3) { defaultVideoOffset = videoDuration * 1 / 3; } } } string tempFileName = FileUtils.GetTempFileName(".jpg"); string arguments = string.Format("-ss {0} -i \"{1}\" -vframes 1 -an -dn -vf \"yadif='mode=send_frame:parity=auto:deint=all',scale=iw*sar:ih,setsar=1/1{3}\" -y \"{2}\"", defaultVideoOffset, // Calling EnsureLocalFileSystemAccess not necessary; access for external process ensured by ExecuteWithResourceAccess lfsra.LocalFileSystemPath, tempFileName, downscale); //ServiceRegistration.Get<ILogger>().Info("VideoThumbnailer: FFMpeg {0} {1}", executable, arguments); await FFMPEG_THROTTLE_LOCK.WaitAsync().ConfigureAwait(false); try { ProcessExecutionResult executionResult = await FFMpegBinary.FFMpegExecuteWithResourceAccessAsync(lfsra, arguments, ProcessPriorityClass.BelowNormal, PROCESS_TIMEOUT_MS).ConfigureAwait(false); if (executionResult.Success && File.Exists(tempFileName)) { var binary = FileUtils.ReadFile(tempFileName); MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, binary); // Calling EnsureLocalFileSystemAccess not necessary; only string operation ServiceRegistration.Get <ILogger>().Info("VideoThumbnailer: Successfully created thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } else { // Calling EnsureLocalFileSystemAccess not necessary; only string operation ServiceRegistration.Get <ILogger>().Warn("VideoThumbnailer: Failed to create thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); ServiceRegistration.Get <ILogger>().Debug("VideoThumbnailer: FFMpeg failure {0} dump:\n{1}", executionResult.ExitCode, executionResult.StandardError); } } catch (TaskCanceledException) { ServiceRegistration.Get <ILogger>().Warn("VideoThumbnailer: External process aborted due to timeout: Executable='{0}', Arguments='{1}'", FFMpegBinary.FFMpegPath, arguments); } finally { FFMPEG_THROTTLE_LOCK.Release(); try { if (File.Exists(tempFileName)) { File.Delete(tempFileName); } } catch { } } return(true); }
public static bool IsExtracted(ILocalFsResourceAccessor accessor, string selectedItem, out string extractedPath) { extractedPath = GetExtractionPath(accessor.CanonicalLocalResourcePath.LastPathSegment.Path, selectedItem); return(File.Exists(extractedPath)); }
public void SetMediaItem(IResourceLocator locator, string mediaItemTitle) { // free previous opened resource FilterGraphTools.TryDispose(ref _resourceAccessor); FilterGraphTools.TryDispose(ref _rot); _state = PlayerState.Active; _isPaused = true; try { _resourceLocator = locator; _mediaItemTitle = mediaItemTitle; _resourceAccessor = _resourceLocator.CreateLocalFsAccessor(); ServiceRegistration.Get<ILogger>().Debug("{0}: Initializing for media item '{1}'", PlayerTitle, _resourceAccessor.LocalFileSystemPath); // Create a DirectShow FilterGraph CreateGraphBuilder(); // Add it in ROT (Running Object Table) for debug purpose, it allows to view the Graph from outside (i.e. graphedit) _rot = new DsROTEntry(_graphBuilder); // Add a notification handler (see WndProc) _instancePtr = Marshal.AllocCoTaskMem(4); IMediaEventEx mee = _graphBuilder as IMediaEventEx; if (mee != null) mee.SetNotifyWindow(SkinContext.Form.Handle, WM_GRAPHNOTIFY, _instancePtr); // Create the Allocator / Presenter object FreeEvrCallback(); CreateEvrCallback(); AddEvr(); ServiceRegistration.Get<ILogger>().Debug("{0}: Adding audio renderer", PlayerTitle); AddAudioRenderer(); ServiceRegistration.Get<ILogger>().Debug("{0}: Adding preferred codecs", PlayerTitle); AddPreferredCodecs(); ServiceRegistration.Get<ILogger>().Debug("{0}: Adding file source", PlayerTitle); AddFileSource(); ServiceRegistration.Get<ILogger>().Debug("{0}: Run graph", PlayerTitle); //This needs to be done here before we check if the evr pins are connected //since this method gives players the chance to render the last bits of the graph OnBeforeGraphRunning(); // Now run the graph, i.e. the DVD player needs a running graph before getting informations from dvd filter. IMediaControl mc = (IMediaControl) _graphBuilder; int hr = mc.Run(); DsError.ThrowExceptionForHR(hr); _initialized = true; OnGraphRunning(); } catch (Exception) { Shutdown(); throw; } }
private bool ExtractMovieData(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData, bool importOnly) { // Calling EnsureLocalFileSystemAccess not necessary; only string operation string[] pathsToTest = new[] { lfsra.LocalFileSystemPath, lfsra.CanonicalLocalResourcePath.ToString() }; string title = null; string sortTitle = null; // 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(MovieAspect.ASPECT_ID)) { refresh = true; } MovieInfo movieInfo = new MovieInfo(); if (refresh) { movieInfo.FromMetadata(extractedAspectData); } if (movieInfo.MovieName.IsEmpty) { //Try to get title if (MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, out title) && !string.IsNullOrEmpty(title) && !lfsra.ResourceName.StartsWith(title, StringComparison.InvariantCultureIgnoreCase)) { movieInfo.MovieName = title; /* Clear the names from unwanted strings */ MovieNameMatcher.CleanupTitle(movieInfo); } } if (movieInfo.MovieNameSort.IsEmpty) { //Try to get sort title if (MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_SORT_TITLE, out sortTitle) && !string.IsNullOrEmpty(sortTitle)) { movieInfo.MovieNameSort = sortTitle; } } if (movieInfo.MovieDbId == 0) { try { // Try to use an existing TMDB id for exact mapping string tmdbId; if (MatroskaMatcher.TryMatchTmdbId(lfsra, out tmdbId)) { movieInfo.MovieDbId = Convert.ToInt32(tmdbId); } } catch (Exception ex) { ServiceRegistration.Get <ILogger>().Debug("MoviesMetadataExtractor: Exception reading TMDB ID for '{0}'", ex, lfsra.CanonicalLocalResourcePath); } } if (string.IsNullOrEmpty(movieInfo.ImdbId)) { try { // Try to use an existing IMDB id for exact mapping string imdbId = null; if (pathsToTest.Any(path => MatroskaMatcher.TryMatchImdbId(lfsra, out imdbId))) { movieInfo.ImdbId = imdbId; } else if (pathsToTest.Any(path => ImdbIdMatcher.TryMatchImdbId(path, out imdbId))) { movieInfo.ImdbId = imdbId; } } catch (Exception ex) { ServiceRegistration.Get <ILogger>().Debug("MoviesMetadataExtractor: Exception reading IMDB ID for '{0}'", ex, lfsra.CanonicalLocalResourcePath); } } if (!movieInfo.IsBaseInfoPresent) { // Also test the full path year. This is useful if the path contains the real name and year. foreach (string path in pathsToTest) { if (MovieNameMatcher.MatchTitleYear(path, movieInfo)) { break; } } //Fall back to MediaAspect.ATTR_TITLE if (movieInfo.MovieName.IsEmpty && !string.IsNullOrEmpty(title)) { movieInfo.MovieName = title; } /* Clear the names from unwanted strings */ MovieNameMatcher.CleanupTitle(movieInfo); } if (!movieInfo.ReleaseDate.HasValue && !movieInfo.HasExternalId) { // When searching movie title, the year can be relevant for multiple titles with same name but different years DateTime recordingDate; if (MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, out recordingDate)) { movieInfo.ReleaseDate = recordingDate; } } // Allow the online lookup to choose best matching language for metadata if (movieInfo.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) && !movieInfo.Languages.Contains(language)) { movieInfo.Languages.Add(language); } } } } if (importOnly) { try { MatroskaMatcher.ExtractFromTags(lfsra, movieInfo); MP4Matcher.ExtractFromTags(lfsra, movieInfo); } catch (Exception ex) { ServiceRegistration.Get <ILogger>().Debug("MoviesMetadataExtractor: Exception reading tags for '{0}'", ex, lfsra.CanonicalLocalResourcePath); } } if (SkipOnlineSearches && !SkipFanArtDownload) { MovieInfo tempInfo = movieInfo.Clone(); OnlineMatcherService.Instance.FindAndUpdateMovie(tempInfo, importOnly); movieInfo.CopyIdsFrom(tempInfo); movieInfo.HasChanged = tempInfo.HasChanged; } else if (!SkipOnlineSearches) { OnlineMatcherService.Instance.FindAndUpdateMovie(movieInfo, importOnly); } //Send it to the videos section if (!SkipOnlineSearches && !movieInfo.HasExternalId) { return(false); } if (importOnly) { //Create custom collection (overrides online collection) MovieCollectionInfo collectionInfo = movieInfo.CloneBasicInstance <MovieCollectionInfo>(); string collectionName; if (string.IsNullOrEmpty(collectionInfo.NameId) && CollectionFolderHasFanArt(lfsra, out collectionName)) { collectionInfo = new MovieCollectionInfo(); collectionInfo.CollectionName = collectionName; if (!collectionInfo.CollectionName.IsEmpty) { movieInfo.CollectionName = collectionInfo.CollectionName; movieInfo.CopyIdsFrom(collectionInfo); //Reset ID's movieInfo.HasChanged = true; } } } movieInfo.AssignNameId(); if (refresh) { if ((IncludeActorDetails && !BaseInfo.HasRelationship(extractedAspectData, PersonAspect.ROLE_ACTOR) && movieInfo.Actors.Count > 0) || (IncludeCharacterDetails && !BaseInfo.HasRelationship(extractedAspectData, CharacterAspect.ROLE_CHARACTER) && movieInfo.Characters.Count > 0) || (IncludeDirectorDetails && !BaseInfo.HasRelationship(extractedAspectData, PersonAspect.ROLE_DIRECTOR) && movieInfo.Directors.Count > 0) || (IncludeWriterDetails && !BaseInfo.HasRelationship(extractedAspectData, PersonAspect.ROLE_WRITER) && movieInfo.Writers.Count > 0) || (IncludeProductionCompanyDetails && !BaseInfo.HasRelationship(extractedAspectData, CompanyAspect.ROLE_COMPANY) && movieInfo.ProductionCompanies.Count > 0)) { movieInfo.HasChanged = true; } } if (!movieInfo.HasChanged && !importOnly) { return(false); } movieInfo.SetMetadata(extractedAspectData); return(movieInfo.IsBaseInfoPresent); }
private bool ExtractThumbnail(ILocalFsResourceAccessor lfsra, IDictionary <Guid, MediaItemAspect> extractedAspectData) { // We can only work on files and make sure this file was detected by a lower MDE before (title is set then). // VideoAspect must be present to be sure it is actually a video resource. if (!lfsra.IsFile || !extractedAspectData.ContainsKey(VideoAspect.ASPECT_ID)) { return(false); } byte[] thumb; // We only want to create missing thumbnails here, so check for existing ones first if (MediaItemAspect.TryGetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, out thumb) && thumb != null) { return(true); } // Check for a reasonable time offset long defaultVideoOffset = 720; long videoDuration; if (MediaItemAspect.TryGetAttribute(extractedAspectData, VideoAspect.ATTR_DURATION, out videoDuration)) { if (defaultVideoOffset > videoDuration * 1 / 3) { defaultVideoOffset = videoDuration * 1 / 3; } } // ToDo: Move creation of temp file names to FileUtils class string tempFileName = Path.GetTempPath() + Guid.NewGuid() + ".jpg"; string executable = FileUtils.BuildAssemblyRelativePath("ffmpeg.exe"); string arguments = string.Format("-ss {0} -i \"{1}\" -vframes 1 -an -dn -vf \"yadif='mode=send_frame:parity=auto:deint=all',scale=iw*sar:ih,setsar=1/1,scale=iw/2:-1\" -y \"{2}\"", defaultVideoOffset, lfsra.LocalFileSystemPath, tempFileName); try { bool success; lock (FFMPEG_THROTTLE_LOCK) success = ProcessUtils.TryExecute_AutoImpersonate(executable, arguments, ProcessPriorityClass.Idle, PROCESS_TIMEOUT_MS); if (success && File.Exists(tempFileName)) { var binary = FileUtils.ReadFile(tempFileName); MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, binary); ServiceRegistration.Get <ILogger>().Info("VideoThumbnailer: Successfully created thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } else { ServiceRegistration.Get <ILogger>().Warn("VideoThumbnailer: Failed to create thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } } finally { if (File.Exists(tempFileName)) { File.Delete(tempFileName); } } return(true); }
private void ExtractMkvImages(ILocalFsResourceAccessor lfsra, Guid?seriesMediaItemId, SeriesInfo series) { if (!seriesMediaItemId.HasValue) { return; } string mediaItemId = seriesMediaItemId.Value.ToString().ToUpperInvariant(); string fileSystemPath = string.Empty; IDictionary <string, string> patterns = new Dictionary <string, string>() { { "banner.", FanArtTypes.Banner }, { "clearart.", FanArtTypes.ClearArt }, { "cover.", FanArtTypes.Cover }, { "poster.", FanArtTypes.Poster }, { "folder.", FanArtTypes.Poster }, { "backdrop.", FanArtTypes.FanArt }, { "fanart.", FanArtTypes.FanArt }, }; // File based access try { if (lfsra != null) { fileSystemPath = lfsra.LocalFileSystemPath; var ext = ResourcePathHelper.GetExtension(lfsra.LocalFileSystemPath); if (!MKV_EXTENSIONS.Contains(ext)) { return; } MatroskaInfoReader mkvReader = new MatroskaInfoReader(lfsra); foreach (string pattern in patterns.Keys) { byte[] binaryData; if (mkvReader.GetAttachmentByName(pattern, out binaryData)) { string fanArtType = patterns[pattern]; using (FanArtCache.FanArtCountLock countLock = FanArtCache.GetFanArtCountLock(mediaItemId, fanArtType)) { if (countLock.Count >= FanArtCache.MAX_FANART_IMAGES[fanArtType]) { return; } FanArtCache.InitFanArtCache(mediaItemId, series.ToString()); string cacheFile = GetCacheFileName(mediaItemId, fanArtType, "File." + pattern + Path.GetFileNameWithoutExtension(lfsra.LocalFileSystemPath) + ".jpg"); if (!File.Exists(cacheFile)) { using (MemoryStream ms = new MemoryStream(binaryData)) { using (Image img = Image.FromStream(ms, true, true)) { img.Save(cacheFile, System.Drawing.Imaging.ImageFormat.Jpeg); countLock.Count++; } } } } } } } } catch (Exception ex) { Logger.Warn("SeriesFanArtHandler: Exception while reading mkv attachments from '{0}'", ex, fileSystemPath); } }
public bool TryGetFanArt(string mediaType, string fanArtType, string name, int maxWidth, int maxHeight, bool singleRandom, out IList <FanArtImage> result) { result = null; if (mediaType != FanArtMediaTypes.Episode && mediaType != FanArtMediaTypes.Movie) { return(false); } 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, null, true); if (items == null || items.Count == 0) { return(false); } MediaItem mediaItem = items.First(); // Virtual resources won't have any local fanart if (mediaItem.IsVirtual) { return(false); } var resourceLocator = mediaItem.GetResourceLocator(); 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.Undefined: 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; case FanArtTypes.Logo: patterns.Add("clearlogo."); break; default: return(false); } // File based access try { 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); } MatroskaBinaryReader mkvReader = new MatroskaBinaryReader(fsra); foreach (string pattern in patterns) { byte[] binaryData = mkvReader.GetAttachmentByNameAsync(pattern).Result; if (binaryData != null) { 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); }
private bool ExtractThumbnail(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData) { // We can only work on files and make sure this file was detected by a lower MDE before (title is set then). // VideoAspect must be present to be sure it is actually a video resource. if (!lfsra.IsFile || !extractedAspectData.ContainsKey(VideoStreamAspect.ASPECT_ID)) { return(false); } byte[] thumb; // We only want to create missing thumbnails here, so check for existing ones first if (MediaItemAspect.TryGetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, out thumb) && thumb != null) { return(false); } //ServiceRegistration.Get<ILogger>().Info("VideoThumbnailer: Evaluate {0}", lfsra.ResourceName); bool isPrimaryResource = false; IList <MultipleMediaItemAspect> resourceAspects; if (MediaItemAspect.TryGetAspects(extractedAspectData, ProviderResourceAspect.Metadata, out resourceAspects)) { foreach (MultipleMediaItemAspect pra in resourceAspects) { string accessorPath = (string)pra.GetAttributeValue(ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH); ResourcePath resourcePath = ResourcePath.Deserialize(accessorPath); if (resourcePath.Equals(lfsra.CanonicalLocalResourcePath)) { if (pra.GetAttributeValue <bool?>(ProviderResourceAspect.ATTR_PRIMARY) == true) { isPrimaryResource = true; break; } } } } if (!isPrimaryResource) //Ignore subtitles { return(false); } // Check for a reasonable time offset long defaultVideoOffset = 720; long videoDuration; string downscale = ",scale=iw/2:-1"; // Reduces the video frame size to a half of original IList <MultipleMediaItemAspect> videoAspects; if (MediaItemAspect.TryGetAspects(extractedAspectData, VideoStreamAspect.Metadata, out videoAspects)) { if ((videoDuration = videoAspects[0].GetAttributeValue <long>(VideoStreamAspect.ATTR_DURATION)) > 0) { if (defaultVideoOffset > videoDuration * 1 / 3) { defaultVideoOffset = videoDuration * 1 / 3; } } int videoWidth = videoAspects[0].GetAttributeValue <int>(VideoStreamAspect.ATTR_WIDTH); // Don't downscale SD video frames, quality is already quite low. if (videoWidth > 0 && videoWidth <= 720) { downscale = ""; } } // ToDo: Move creation of temp file names to FileUtils class string tempFileName = Path.GetTempPath() + Guid.NewGuid() + ".jpg"; string executable = FileUtils.BuildAssemblyRelativePath("ffmpeg.exe"); string arguments = string.Format("-ss {0} -i \"{1}\" -vframes 1 -an -dn -vf \"yadif='mode=send_frame:parity=auto:deint=all',scale=iw*sar:ih,setsar=1/1{3}\" -y \"{2}\"", defaultVideoOffset, // Calling EnsureLocalFileSystemAccess not necessary; access for external process ensured by ExecuteWithResourceAccess lfsra.LocalFileSystemPath, tempFileName, downscale); //ServiceRegistration.Get<ILogger>().Info("VideoThumbnailer: FFMpeg {0} {1}", executable, arguments); try { Task <ProcessExecutionResult> executionResult = null; FFMPEG_THROTTLE_LOCK.Wait(); executionResult = FFMpegBinary.FFMpegExecuteWithResourceAccessAsync(lfsra, arguments, ProcessPriorityClass.BelowNormal, PROCESS_TIMEOUT_MS); if (executionResult.Result.Success && File.Exists(tempFileName)) { var binary = FileUtils.ReadFile(tempFileName); MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, binary); // Calling EnsureLocalFileSystemAccess not necessary; only string operation ServiceRegistration.Get <ILogger>().Info("VideoThumbnailer: Successfully created thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } else { // Calling EnsureLocalFileSystemAccess not necessary; only string operation ServiceRegistration.Get <ILogger>().Warn("VideoThumbnailer: Failed to create thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); ServiceRegistration.Get <ILogger>().Debug("VideoThumbnailer: FFMpeg failure {0} dump:\n{1}", executionResult.Result.ExitCode, executionResult.Result.StandardError); } } catch (AggregateException ae) { ae.Handle(e => { if (e is TaskCanceledException) { ServiceRegistration.Get <ILogger>().Warn("VideoThumbnailer.ExtractThumbnail: External process aborted due to timeout: Executable='{0}', Arguments='{1}', Timeout='{2}'", executable, arguments, PROCESS_TIMEOUT_MS); return(true); } return(false); }); } finally { FFMPEG_THROTTLE_LOCK.Release(); try { if (File.Exists(tempFileName)) { File.Delete(tempFileName); } } catch { } } return(true); }
/// <summary> /// Tries to match series by checking the <paramref name="folderOrFileLfsra"/> for known patterns. The match is only successful, /// if the <see cref="SeriesInfo.IsCompleteMatch"/> is <c>true</c>. /// </summary> /// <param name="folderOrFileLfsra"><see cref="ILocalFsResourceAccessor"/> to file</param> /// <param name="seriesInfo">Returns the parsed SeriesInfo</param> /// <returns><c>true</c> if successful.</returns> public bool MatchSeries(ILocalFsResourceAccessor folderOrFileLfsra, out SeriesInfo seriesInfo) { return MatchSeries(folderOrFileLfsra.LocalFileSystemPath, out seriesInfo); }
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> /// 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="episodeInfo">Returns the parsed EpisodeInfo</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 async Task <bool> MatchSeriesAsync(ILocalFsResourceAccessor folderOrFileLfsra, EpisodeInfo episodeInfo) { // Calling EnsureLocalFileSystemAccess not necessary; only string operation string extensionLower = StringUtils.TrimToEmpty(Path.GetExtension(folderOrFileLfsra.LocalFileSystemPath)).ToLower(); if (!MatroskaConsts.MATROSKA_VIDEO_EXTENSIONS.Contains(extensionLower)) { return(false); } MatroskaBinaryReader mkvReader = new MatroskaBinaryReader(folderOrFileLfsra); // Add keys to be extracted to tags dictionary, matching results will returned as value Dictionary <string, IList <string> > tagsToExtract = MatroskaConsts.DefaultVideoTags; await mkvReader.ReadTagsAsync(tagsToExtract).ConfigureAwait(false); IList <string> tags = tagsToExtract[MatroskaConsts.TAG_EPISODE_SUMMARY]; string plot = tags != null?tags.FirstOrDefault() : string.Empty; if (!string.IsNullOrEmpty(plot)) { episodeInfo.HasChanged |= MetadataUpdater.SetOrUpdateString(ref episodeInfo.Summary, plot, true); } // Series and episode handling. Prefer information from tags. if (tagsToExtract[MatroskaConsts.TAG_EPISODE_TITLE] != null) { string title = tagsToExtract[MatroskaConsts.TAG_EPISODE_TITLE].FirstOrDefault(); if (!string.IsNullOrEmpty(title)) { title = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(title); episodeInfo.HasChanged |= MetadataUpdater.SetOrUpdateString(ref episodeInfo.EpisodeName, title, true); } } if (tagsToExtract[MatroskaConsts.TAG_SERIES_TITLE] != null) { string title = tagsToExtract[MatroskaConsts.TAG_SERIES_TITLE].FirstOrDefault(); if (!string.IsNullOrEmpty(title)) { title = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(title); episodeInfo.HasChanged |= MetadataUpdater.SetOrUpdateString(ref episodeInfo.SeriesName, title, true); } } if (tagsToExtract[MatroskaConsts.TAG_SERIES_IMDB_ID] != null) { string imdbId; foreach (string candidate in tagsToExtract[MatroskaConsts.TAG_SERIES_IMDB_ID]) { if (ImdbIdMatcher.TryMatchImdbId(candidate, out imdbId)) { episodeInfo.HasChanged |= MetadataUpdater.SetOrUpdateId(ref episodeInfo.SeriesImdbId, imdbId); break; } } } if (tagsToExtract[MatroskaConsts.TAG_SERIES_ACTORS] != null) { episodeInfo.HasChanged |= MetadataUpdater.SetOrUpdateList(episodeInfo.Actors, tagsToExtract[MatroskaConsts.TAG_SERIES_ACTORS].Select(t => new PersonInfo() { Name = t, Occupation = PersonAspect.OCCUPATION_ACTOR, MediaName = episodeInfo.EpisodeName.Text, ParentMediaName = episodeInfo.SeriesName.Text }).ToList(), false); } // On Series, the counting tag is "TVDB" if (tagsToExtract[MatroskaConsts.TAG_SERIES_TVDB_ID] != null) { int tmp; foreach (string candidate in tagsToExtract[MatroskaConsts.TAG_SERIES_TVDB_ID]) { if (int.TryParse(candidate, out tmp) == true) { episodeInfo.HasChanged |= MetadataUpdater.SetOrUpdateId(ref episodeInfo.SeriesTvdbId, tmp); break; } } } int tmpInt; if (tagsToExtract[MatroskaConsts.TAG_SEASON_NUMBER] != null && int.TryParse(tagsToExtract[MatroskaConsts.TAG_SEASON_NUMBER].FirstOrDefault(), out tmpInt)) { episodeInfo.HasChanged |= MetadataUpdater.SetOrUpdateValue(ref episodeInfo.SeasonNumber, tmpInt); } if (tagsToExtract[MatroskaConsts.TAG_EPISODE_NUMBER] != null) { int episodeNum; foreach (string s in tagsToExtract[MatroskaConsts.TAG_EPISODE_NUMBER]) { if (int.TryParse(s, out episodeNum)) { if (!episodeInfo.EpisodeNumbers.Contains(episodeNum)) { episodeInfo.EpisodeNumbers.Add(episodeNum); } } } } return(true); }
/// <summary> /// Tries to match series by checking the <paramref name="folderOrFileLfsra"/> for known patterns. The match is only successful, /// if the <see cref="EpisodeInfo.IsBaseInfoPresent"/> is <c>true</c>. /// </summary> /// <param name="folderOrFileLfsra"><see cref="ILocalFsResourceAccessor"/> to file</param> /// <param name="episodeInfo">Returns the parsed EpisodeInfo</param> /// <returns><c>true</c> if successful.</returns> public bool MatchSeries(ILocalFsResourceAccessor folderOrFileLfsra, EpisodeInfo episodeInfo) { return(MatchSeries(folderOrFileLfsra.LocalFileSystemPath, episodeInfo)); }
private async Task <bool> ExtractMovieData(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); } // Calling EnsureLocalFileSystemAccess not necessary; only string operation string[] pathsToTest = new[] { lfsra.LocalFileSystemPath, lfsra.CanonicalLocalResourcePath.ToString() }; string title = null; string sortTitle = null; bool isReimport = extractedAspectData.ContainsKey(ReimportAspect.ASPECT_ID); MovieInfo movieInfo = new MovieInfo(); if (extractedAspectData.ContainsKey(MovieAspect.ASPECT_ID)) { movieInfo.FromMetadata(extractedAspectData); } if (movieInfo.MovieName.IsEmpty) { //Try to get title if (MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, out title) && !string.IsNullOrEmpty(title) && !lfsra.ResourceName.StartsWith(title, StringComparison.InvariantCultureIgnoreCase)) { //The title may still contain tags and other noise, try and parse it for a title and year. MovieNameMatcher.MatchTitleYear(title, movieInfo); } } if (movieInfo.MovieNameSort.IsEmpty) { //Try to get sort title if (MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_SORT_TITLE, out sortTitle) && !string.IsNullOrEmpty(sortTitle)) { movieInfo.MovieNameSort = sortTitle; } } if (!isReimport) //Ignore tags or file based information for reimport because they might be the cause of the wrong import { if (movieInfo.MovieDbId == 0) { try { // Try to use an existing TMDB id for exact mapping string tmdbId = await MatroskaMatcher.TryMatchTmdbIdAsync(lfsra).ConfigureAwait(false); if (!string.IsNullOrEmpty(tmdbId)) { movieInfo.MovieDbId = Convert.ToInt32(tmdbId); } } catch (Exception ex) { ServiceRegistration.Get <ILogger>().Debug("MoviesMetadataExtractor: Exception reading TMDB ID for '{0}'", ex, lfsra.CanonicalLocalResourcePath); } } if (string.IsNullOrEmpty(movieInfo.ImdbId)) { try { // Try to use an existing IMDB id for exact mapping string imdbId = await MatroskaMatcher.TryMatchImdbIdAsync(lfsra).ConfigureAwait(false); if (!string.IsNullOrEmpty(imdbId)) { movieInfo.ImdbId = imdbId; } else if (pathsToTest.Any(path => ImdbIdMatcher.TryMatchImdbId(path, out imdbId))) { movieInfo.ImdbId = imdbId; } } catch (Exception ex) { ServiceRegistration.Get <ILogger>().Debug("MoviesMetadataExtractor: Exception reading IMDB ID for '{0}'", ex, lfsra.CanonicalLocalResourcePath); } } if (!movieInfo.IsBaseInfoPresent || !movieInfo.ReleaseDate.HasValue) { // Also test the full path year. This is useful if the path contains the real name and year. foreach (string path in pathsToTest) { if (MovieNameMatcher.MatchTitleYear(path, movieInfo)) { break; } } //Fall back to MediaAspect.ATTR_TITLE if (movieInfo.MovieName.IsEmpty && !string.IsNullOrEmpty(title)) { movieInfo.MovieName = title; } /* Clear the names from unwanted strings */ MovieNameMatcher.CleanupTitle(movieInfo); } if (!movieInfo.ReleaseDate.HasValue && !movieInfo.HasExternalId) { // When searching movie title, the year can be relevant for multiple titles with same name but different years DateTime recordingDate; if (MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, out recordingDate)) { movieInfo.ReleaseDate = recordingDate; } } try { await MatroskaMatcher.ExtractFromTagsAsync(lfsra, movieInfo).ConfigureAwait(false); MP4Matcher.ExtractFromTags(lfsra, movieInfo); } catch (Exception ex) { ServiceRegistration.Get <ILogger>().Debug("MoviesMetadataExtractor: Exception reading tags for '{0}'", ex, lfsra.CanonicalLocalResourcePath); } } // Allow the online lookup to choose best matching language for metadata if (movieInfo.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) && !movieInfo.Languages.Contains(language)) { movieInfo.Languages.Add(language); } } } } if (SkipOnlineSearches && !SkipFanArtDownload) { MovieInfo tempInfo = movieInfo.Clone(); if (await OnlineMatcherService.Instance.FindAndUpdateMovieAsync(tempInfo).ConfigureAwait(false)) { movieInfo.CopyIdsFrom(tempInfo); movieInfo.HasChanged = tempInfo.HasChanged; } } else if (!SkipOnlineSearches) { await OnlineMatcherService.Instance.FindAndUpdateMovieAsync(movieInfo).ConfigureAwait(false); } //Asign genre ids if (movieInfo.Genres.Count > 0) { IGenreConverter converter = ServiceRegistration.Get <IGenreConverter>(); foreach (var genre in movieInfo.Genres) { if (!genre.Id.HasValue && converter.GetGenreId(genre.Name, GenreCategory.Movie, null, out int genreId)) { genre.Id = genreId; movieInfo.HasChanged = true; } } } //Send it to the videos section if (!SkipOnlineSearches && !movieInfo.HasExternalId) { return(false); } //Create custom collection (overrides online collection) MovieCollectionInfo collectionInfo = movieInfo.CloneBasicInstance <MovieCollectionInfo>(); string collectionName; if (string.IsNullOrEmpty(collectionInfo.NameId) && CollectionFolderHasFanArt(lfsra, out collectionName)) { collectionInfo = new MovieCollectionInfo(); collectionInfo.CollectionName = collectionName; if (!collectionInfo.CollectionName.IsEmpty) { movieInfo.CollectionName = collectionInfo.CollectionName; movieInfo.CopyIdsFrom(collectionInfo); //Reset ID's movieInfo.HasChanged = true; } } if (movieInfo.MovieNameSort.IsEmpty) { if (!movieInfo.CollectionName.IsEmpty && movieInfo.ReleaseDate.HasValue) { movieInfo.MovieNameSort = $"{movieInfo.CollectionName.Text} {movieInfo.ReleaseDate.Value.Year}-{movieInfo.ReleaseDate.Value.Month.ToString("00")}"; } else if (!movieInfo.MovieName.IsEmpty) { movieInfo.MovieNameSort = BaseInfo.GetSortTitle(movieInfo.MovieName.Text); } else { movieInfo.MovieNameSort = BaseInfo.GetSortTitle(title); } } movieInfo.SetMetadata(extractedAspectData); return(movieInfo.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); }
protected virtual Task <bool> ExtractMetadataAsync(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData, bool forceQuickMode) { if (!CanExtract(lfsra, extractedAspectData)) { return(Task.FromResult(false)); } using (var rec = new MCRecMetadataEditor(lfsra.LocalFileSystemPath)) { // Handle series information IDictionary tags = rec.GetAttributes(); // Force MimeType IList <MultipleMediaItemAspect> providerAspects; MediaItemAspect.TryGetAspects(extractedAspectData, ProviderResourceAspect.Metadata, out providerAspects); foreach (MultipleMediaItemAspect aspect in providerAspects) { aspect.SetAttribute(ProviderResourceAspect.ATTR_MIME_TYPE, "slimtv/wtv"); } MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_ISVIRTUAL, false); MediaItemAspect.SetAttribute(extractedAspectData, VideoAspect.ATTR_ISDVD, false); string value; if (TryGet(tags, TAG_TITLE, out value) && !string.IsNullOrEmpty(value)) { MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, value); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_SORT_TITLE, BaseInfo.GetSortTitle(value)); } if (TryGet(tags, TAG_GENRE, out value)) { List <GenreInfo> genreList = new List <GenreInfo>(value.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries).Select(s => new GenreInfo { Name = s.Trim() })); IGenreConverter converter = ServiceRegistration.Get <IGenreConverter>(); foreach (var genre in genreList) { if (!genre.Id.HasValue && converter.GetGenreId(genre.Name, GenreCategory.Movie, null, out int genreId)) { genre.Id = genreId; } } foreach (GenreInfo genre in genreList) { MultipleMediaItemAspect genreAspect = MediaItemAspect.CreateAspect(extractedAspectData, GenreAspect.Metadata); genreAspect.SetAttribute(GenreAspect.ATTR_ID, genre.Id); genreAspect.SetAttribute(GenreAspect.ATTR_GENRE, genre.Name); } } if (TryGet(tags, TAG_PLOT, out value)) { MediaItemAspect.SetAttribute(extractedAspectData, VideoAspect.ATTR_STORYPLOT, value); } if (TryGet(tags, TAG_ORIGINAL_TIME, out value)) { DateTime origTime; if (DateTime.TryParse(value, out origTime)) { MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, origTime); } } if (TryGet(tags, TAG_CHANNEL, out value)) { MediaItemAspect.SetAttribute(extractedAspectData, RecordingAspect.ATTR_CHANNEL, value); } long lValue; if (TryGet(tags, TAG_STARTTIME, out lValue)) { MediaItemAspect.SetAttribute(extractedAspectData, RecordingAspect.ATTR_STARTTIME, FromMCEFileTime(lValue)); } if (TryGet(tags, TAG_ENDTIME, out lValue)) { MediaItemAspect.SetAttribute(extractedAspectData, RecordingAspect.ATTR_ENDTIME, FromMCEFileTime(lValue)); } } return(Task.FromResult(true)); }
private static bool ExtractMovieData(ILocalFsResourceAccessor lfsra, IDictionary <Guid, MediaItemAspect> extractedAspectData) { // Calling EnsureLocalFileSystemAccess not necessary; only string operation string[] pathsToTest = new[] { lfsra.LocalFileSystemPath, lfsra.CanonicalLocalResourcePath.ToString() }; string title; // VideoAspect must be present to be sure it is actually a video resource. if (!extractedAspectData.ContainsKey(VideoAspect.ASPECT_ID)) { return(false); } if (!MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, out title) || string.IsNullOrEmpty(title)) { return(false); } MovieInfo movieInfo = new MovieInfo { MovieName = title, }; // Allow the online lookup to choose best matching language for metadata ICollection <string> movieLanguages; if (MediaItemAspect.TryGetAttribute(extractedAspectData, VideoAspect.ATTR_AUDIOLANGUAGES, out movieLanguages) && movieLanguages.Count > 0) { movieInfo.Languages.AddRange(movieLanguages); } // Try to use an existing IMDB id for exact mapping string imdbId; if (MediaItemAspect.TryGetAttribute(extractedAspectData, MovieAspect.ATTR_IMDB_ID, out imdbId) || pathsToTest.Any(path => ImdbIdMatcher.TryMatchImdbId(path, out imdbId)) || NfoReader.TryMatchImdbId(lfsra, out imdbId) || MatroskaMatcher.TryMatchImdbId(lfsra, out imdbId)) { movieInfo.ImdbId = imdbId; } // Also test the full path year, using a dummy. This is useful if the path contains the real name and year. foreach (string path in pathsToTest) { MovieInfo dummy = new MovieInfo { MovieName = path }; if (NamePreprocessor.MatchTitleYear(dummy)) { movieInfo.MovieName = dummy.MovieName; movieInfo.Year = dummy.Year; break; } } // When searching movie title, the year can be relevant for multiple titles with same name but different years DateTime recordingDate; if (MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, out recordingDate)) { movieInfo.Year = recordingDate.Year; } if (MovieTheMovieDbMatcher.Instance.FindAndUpdateMovie(movieInfo)) { movieInfo.SetMetadata(extractedAspectData); return(true); } return(false); }
public bool TryGetFanArt(string mediaType, string fanArtType, string name, int maxWidth, int maxHeight, bool singleRandom, out IList <FanArtImage> result) { result = null; if ((mediaType != FanArtMediaTypes.Episode && mediaType != FanArtMediaTypes.Movie) || (fanArtType != FanArtTypes.Thumbnail && fanArtType != FanArtTypes.Undefined)) { return(false); } 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, null, true); if (items == null || items.Count == 0) { return(false); } MediaItem mediaItem = items.First(); // Virtual resources won't have any local fanart if (mediaItem.IsVirtual) { return(false); } var resourceLocator = mediaItem.GetResourceLocator(); string fileSystemPath = string.Empty; // File based access try { using (var accessor = resourceLocator?.CreateAccessor()) { ILocalFsResourceAccessor lfsra = accessor as ILocalFsResourceAccessor; if (lfsra != null) { // Check for a reasonable time offset int defaultVideoOffset = 720; long videoDuration; double width = 0; double height = 0; double downscale = 7.5; // Reduces the HD video frame size to a quarter size to around 256 IList <MultipleMediaItemAspect> videoAspects; if (MediaItemAspect.TryGetAspects(mediaItem.Aspects, VideoStreamAspect.Metadata, out videoAspects)) { if ((videoDuration = videoAspects[0].GetAttributeValue <long>(VideoStreamAspect.ATTR_DURATION)) > 0) { if (defaultVideoOffset > videoDuration * DEFAULT_OPENCV_THUMBNAIL_OFFSET) { defaultVideoOffset = Convert.ToInt32(videoDuration * DEFAULT_OPENCV_THUMBNAIL_OFFSET); } } width = videoAspects[0].GetAttributeValue <int>(VideoStreamAspect.ATTR_WIDTH); height = videoAspects[0].GetAttributeValue <int>(VideoStreamAspect.ATTR_HEIGHT); downscale = width / 256.0; //256 is max size of large thumbnail aspect } var sw = Stopwatch.StartNew(); using (VideoCapture capture = new VideoCapture()) { capture.Open(lfsra.LocalFileSystemPath); int capturePos = defaultVideoOffset * 1000; if (capture.FrameCount > 0 && capture.Fps > 0) { var duration = capture.FrameCount / capture.Fps; if (defaultVideoOffset > duration) { capturePos = Convert.ToInt32(duration * DEFAULT_OPENCV_THUMBNAIL_OFFSET * 1000); } } if (capture.FrameWidth > 0) { downscale = capture.FrameWidth / MAX_THUMBNAIL_WIDTH; } capture.PosMsec = capturePos; using (var mat = capture.RetrieveMat()) { if (mat.Height > 0 && mat.Width > 0) { width = mat.Width; height = mat.Height; using (var scaledMat = mat.Resize(new OpenCvSharp.Size(width / downscale, height / downscale))) { var binary = scaledMat.ToBytes(); result = new List <FanArtImage> { new FanArtImage(name, binary) }; ServiceRegistration.Get <ILogger>().Debug("OpenCvFanartProvider: Successfully extracted thumbnail for resource '{0}' ({1} ms)", lfsra.LocalFileSystemPath, sw.ElapsedMilliseconds); return(true); } } else { ServiceRegistration.Get <ILogger>().Warn("OpenCvFanartProvider: Failed to extract thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } } } } } } catch (Exception ex) { ServiceRegistration.Get <ILogger>().Warn("OpenCvFanartProvider: Exception while reading thumbnail of type '{0}' from '{1}'", ex, fanArtType, fileSystemPath); } return(false); }
private bool ExtractThumbnail(ILocalFsResourceAccessor lfsra, IDictionary <Guid, MediaItemAspect> extractedAspectData) { // We can only work on files and make sure this file was detected by a lower MDE before (title is set then). // VideoAspect must be present to be sure it is actually a video resource. if (!lfsra.IsFile || !extractedAspectData.ContainsKey(VideoAspect.ASPECT_ID)) { return(false); } byte[] thumb; // We only want to create missing thumbnails here, so check for existing ones first if (MediaItemAspect.TryGetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, out thumb) && thumb != null) { return(true); } // Check for a reasonable time offset long defaultVideoOffset = 720; long videoDuration; if (MediaItemAspect.TryGetAttribute(extractedAspectData, VideoAspect.ATTR_DURATION, out videoDuration)) { if (defaultVideoOffset > videoDuration * 1 / 3) { defaultVideoOffset = videoDuration * 1 / 3; } } string downscale = ",scale=iw/2:-1"; // Reduces the video frame size to a half of original int videoWidth; if (MediaItemAspect.TryGetAttribute(extractedAspectData, VideoAspect.ATTR_WIDTH, out videoWidth)) { // Don't downscale SD video frames, quality is already quite low. if (videoWidth > 0 && videoWidth <= 720) { downscale = ""; } } // ToDo: Move creation of temp file names to FileUtils class string tempFileName = Path.GetTempPath() + Guid.NewGuid() + ".jpg"; string executable = FileUtils.BuildAssemblyRelativePath("ffmpeg.exe"); string arguments = string.Format("-ss {0} -i \"{1}\" -vframes 1 -an -dn -vf \"yadif='mode=send_frame:parity=auto:deint=all',scale=iw*sar:ih,setsar=1/1{3}\" -y \"{2}\"", defaultVideoOffset, // Calling EnsureLocalFileSystemAccess not necessary; access for external process ensured by ExecuteWithResourceAccess lfsra.LocalFileSystemPath, tempFileName, downscale); try { bool success; lock (FFMPEG_THROTTLE_LOCK) success = lfsra.ExecuteWithResourceAccessAsync(executable, arguments, ProcessPriorityClass.Idle, PROCESS_TIMEOUT_MS).Result.Success; if (success && File.Exists(tempFileName)) { var binary = FileUtils.ReadFile(tempFileName); MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, binary); // Calling EnsureLocalFileSystemAccess not necessary; only string operation ServiceRegistration.Get <ILogger>().Info("VideoThumbnailer: Successfully created thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } else { // Calling EnsureLocalFileSystemAccess not necessary; only string operation ServiceRegistration.Get <ILogger>().Warn("VideoThumbnailer: Failed to create thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } } catch (AggregateException ae) { ae.Handle(e => { if (e is TaskCanceledException) { ServiceRegistration.Get <ILogger>().Warn("VideoThumbnailer.ExtractThumbnail: External process aborted due to timeout: Executable='{0}', Arguments='{1}', Timeout='{2}'", executable, arguments, PROCESS_TIMEOUT_MS); return(true); } return(false); }); } finally { if (File.Exists(tempFileName)) { File.Delete(tempFileName); } } return(true); }
public bool TryExtractMetadata(IResourceAccessor mediaItemAccessor, IDictionary <Guid, MediaItemAspect> extractedAspectData, bool forceQuickMode) { try { VideoResult result = null; IFileSystemResourceAccessor fsra = mediaItemAccessor as IFileSystemResourceAccessor; if (fsra == null) { return(false); } if (!fsra.IsFile && fsra.ResourceExists("VIDEO_TS")) { IFileSystemResourceAccessor fsraVideoTs = fsra.GetResource("VIDEO_TS"); if (fsraVideoTs != null && fsraVideoTs.ResourceExists("VIDEO_TS.IFO")) { // Video DVD using (MediaInfoWrapper videoTsInfo = ReadMediaInfo(fsraVideoTs.GetResource("VIDEO_TS.IFO"))) { if (!videoTsInfo.IsValid || videoTsInfo.GetVideoCount() == 0) { return(false); // Invalid video_ts.ifo file } result = VideoResult.CreateDVDInfo(fsra.ResourceName, videoTsInfo); } // Iterate over all video files; MediaInfo finds different audio/video metadata for each .ifo file ICollection <IFileSystemResourceAccessor> files = fsraVideoTs.GetFiles(); if (files != null) { foreach (IFileSystemResourceAccessor file in files) { string lowerPath = (file.ResourcePathName ?? string.Empty).ToLowerInvariant(); if (!lowerPath.EndsWith(".ifo") || lowerPath.EndsWith("video_ts.ifo")) { continue; } using (MediaInfoWrapper mediaInfo = ReadMediaInfo(file)) { // Before we start evaluating the file, check if it is a video at all if (mediaInfo.IsValid && mediaInfo.GetVideoCount() == 0) { continue; } result.AddMediaInfo(mediaInfo); } } } } } else if (fsra.IsFile) { string filePath = fsra.ResourcePathName; if (!HasVideoExtension(filePath)) { return(false); } using (MediaInfoWrapper fileInfo = ReadMediaInfo(fsra)) { // Before we start evaluating the file, check if it is a video at all if (!fileInfo.IsValid || (fileInfo.GetVideoCount() == 0 && !IsWorkaroundRequired(filePath))) { return(false); } string mediaTitle = DosPathHelper.GetFileNameWithoutExtension(fsra.ResourceName); result = VideoResult.CreateFileInfo(mediaTitle, fileInfo); } using (Stream stream = fsra.OpenRead()) result.MimeType = MimeTypeDetector.GetMimeType(stream, DEFAULT_MIMETYPE); } if (result != null) { result.UpdateMetadata(extractedAspectData); using (LocalFsResourceAccessorHelper rah = new LocalFsResourceAccessorHelper(mediaItemAccessor)) { ILocalFsResourceAccessor lfsra = rah.LocalFsResourceAccessor; if (lfsra != null) { MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_SIZE, lfsra.Size); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, lfsra.LastChanged); ExtractMatroskaTags(lfsra, extractedAspectData, forceQuickMode); ExtractMp4Tags(lfsra, extractedAspectData, forceQuickMode); ExtractThumbnailData(lfsra, extractedAspectData, forceQuickMode); } 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("VideoMetadataExtractor: Exception reading resource '{0}' (Text: '{1}')", mediaItemAccessor.CanonicalLocalResourcePath, e.Message); } return(false); }
protected bool ExtractSeriesData(ILocalFsResourceAccessor lfsra, IDictionary<Guid, MediaItemAspect> extractedAspectData) { // VideoAspect must be present to be sure it is actually a video resource. if (!extractedAspectData.ContainsKey(VideoAspect.ASPECT_ID)) return false; SeriesInfo seriesInfo; // Try to get extended information out of matroska files) MatroskaMatcher matroskaMatcher = new MatroskaMatcher(); if (matroskaMatcher.MatchSeries(lfsra, out seriesInfo, ref extractedAspectData)) { ServiceRegistration.Get<ILogger>().Debug("ExtractSeriesData: Found SeriesInformation by MatroskaMatcher for {0}, IMDB {1}, TVDB {2}, IsCompleteMatch {3}", seriesInfo.Series, seriesInfo.ImdbId, seriesInfo.TvdbId, seriesInfo.IsCompleteMatch); } // If no 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(lfsra, 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 override async Task <bool> TryExtractMetadataAsync(IResourceAccessor mediaItemAccessor, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData, bool forceQuickMode) { // If the base AudioMDE already extracted metadata, don't try here again to avoid conflicts. if (extractedAspectData.ContainsKey(AudioAspect.ASPECT_ID)) { return(false); } ILocalFsResourceAccessor fsra = mediaItemAccessor as ILocalFsResourceAccessor; if (fsra == null) { return(false); } if (!fsra.IsFile) { return(false); } string fileName = fsra.ResourceName; if (!HasAudioExtension(fileName)) { return(false); } if (extractedAspectData.ContainsKey(ReimportAspect.ASPECT_ID)) //Ignore for reimports because the tags might be the cause of the wrong match { return(false); } try { TAG_INFO tags; using (fsra.EnsureLocalFileSystemAccess()) tags = BassTags.BASS_TAG_GetFromFile(fsra.LocalFileSystemPath); if (tags == null) { return(false); } fileName = ProviderPathHelper.GetFileNameWithoutExtension(fileName) ?? string.Empty; string title; string artist; uint? trackNo; GuessMetadataFromFileName(fileName, out title, out artist, out trackNo); if (!string.IsNullOrWhiteSpace(tags.title)) { title = tags.title; } IEnumerable <string> artists; if (!string.IsNullOrWhiteSpace(tags.artist)) { artists = SplitTagEnum(tags.artist); artists = PatchID3v23Enumeration(artists); } else { artists = artist == null ? null : new string[] { artist } }; if (!string.IsNullOrWhiteSpace(tags.track) && tags.track != "0") { int iTrackNo; if (int.TryParse(tags.track, out iTrackNo)) { trackNo = (uint?)iTrackNo; } else { trackNo = null; } } TrackInfo trackInfo = new TrackInfo(); if (extractedAspectData.ContainsKey(AudioAspect.ASPECT_ID)) { trackInfo.FromMetadata(extractedAspectData); } else { MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, title); IList <MultipleMediaItemAspect> providerResourceAspect; if (MediaItemAspect.TryGetAspects(extractedAspectData, ProviderResourceAspect.Metadata, out providerResourceAspect)) { providerResourceAspect[0].SetAttribute(ProviderResourceAspect.ATTR_SIZE, fsra.Size); // Calling EnsureLocalFileSystemAccess not necessary; only string operation providerResourceAspect[0].SetAttribute(ProviderResourceAspect.ATTR_MIME_TYPE, "audio/" + Path.GetExtension(fsra.LocalFileSystemPath).Substring(1)); } MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_BITRATE, tags.bitrate); MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_COMMENT, StringUtils.TrimToNull(tags.comment)); MediaItemAspect.SetAttribute(extractedAspectData, AudioAspect.ATTR_DURATION, (long)tags.duration); } if (!trackInfo.IsBaseInfoPresent) { trackInfo.TrackName = title; trackInfo.Album = StringUtils.TrimToNull(tags.album); if (trackNo.HasValue) { trackInfo.TrackNum = (int)trackNo.Value; } trackInfo.Artists = new List <PersonInfo>(); foreach (string artistName in ApplyAdditionalSeparator(artists)) { trackInfo.Artists.Add(new PersonInfo() { Name = artistName, Occupation = PersonAspect.OCCUPATION_ARTIST, ParentMediaName = trackInfo.Album, MediaName = trackInfo.TrackName }); } IEnumerable <string> albumArtists = SplitTagEnum(tags.albumartist); albumArtists = PatchID3v23Enumeration(albumArtists); trackInfo.AlbumArtists = new List <PersonInfo>(); foreach (string artistName in ApplyAdditionalSeparator(albumArtists)) { trackInfo.AlbumArtists.Add(new PersonInfo() { Name = artistName, Occupation = PersonAspect.OCCUPATION_ARTIST, ParentMediaName = trackInfo.Album, MediaName = trackInfo.TrackName }); } IEnumerable <string> composers = SplitTagEnum(tags.composer); composers = PatchID3v23Enumeration(composers); trackInfo.Composers = new List <PersonInfo>(); foreach (string composerName in ApplyAdditionalSeparator(composers)) { trackInfo.Composers.Add(new PersonInfo() { Name = composerName, Occupation = PersonAspect.OCCUPATION_COMPOSER, ParentMediaName = trackInfo.Album, MediaName = trackInfo.TrackName }); } IEnumerable <string> genres = SplitTagEnum(tags.genre); genres = PatchID3v23Enumeration(genres); trackInfo.Genres = ApplyAdditionalSeparator(genres).Where(s => !string.IsNullOrEmpty(s?.Trim())).Select(s => new GenreInfo { Name = s.Trim() }).ToList(); IGenreConverter converter = ServiceRegistration.Get <IGenreConverter>(); foreach (var genre in trackInfo.Genres) { if (!genre.Id.HasValue && converter.GetGenreId(genre.Name, GenreCategory.Music, null, out int genreId)) { genre.Id = genreId; } } int year; if (int.TryParse(tags.year, out year)) { if (year >= 30 && year <= 99) { year += 1900; } if (year >= 1930 && year <= 2030) { trackInfo.ReleaseDate = new DateTime(year, 1, 1); } } if (!trackInfo.HasThumbnail) { // The following code gets cover art images from file (embedded) or from windows explorer cache (supports folder.jpg). if (tags.PictureCount > 0) { try { using (Image cover = tags.PictureGetImage(0)) using (MemoryStream result = new MemoryStream()) { cover.Save(result, ImageFormat.Jpeg); trackInfo.Thumbnail = result.ToArray(); trackInfo.HasChanged = true; } } // Decoding of invalid image data can fail, but main MediaItem is correct. catch { } } } } if (!SkipOnlineSearches && !forceQuickMode) { await OnlineMatcherService.Instance.FindAndUpdateTrackAsync(trackInfo).ConfigureAwait(false); } if (!trackInfo.HasChanged) { return(false); } trackInfo.SetMetadata(extractedAspectData); return(trackInfo.IsBaseInfoPresent); } catch (Exception e) { // Only log at the info level here - And simply return false. This makes the importer know that we // couldn't perform our task here ServiceRegistration.Get <ILogger>().Info("BassAudioMetadataExtractor: Exception reading resource '{0}' (Text: '{1}')", fsra.CanonicalLocalResourcePath, e.Message); } return(false); }
public bool TryGetFanArt(FanArtConstants.FanArtMediaType mediaType, FanArtConstants.FanArtType 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 FanArtConstants.FanArtType.Banner: patterns.Add("banner."); break; case FanArtConstants.FanArtType.ClearArt: patterns.Add("clearart."); break; case FanArtConstants.FanArtType.Poster: case FanArtConstants.FanArtType.Thumbnail: patterns.Add("cover."); patterns.Add("poster."); patterns.Add("folder."); break; case FanArtConstants.FanArtType.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) { fileSystemPath = fsra.LocalFileSystemPath; var ext = Path.GetExtension(fileSystemPath); if (!SUPPORTED_EXTENSIONS.Contains(ext)) { return(false); } MatroskaInfoReader mkvReader = new MatroskaInfoReader(fileSystemPath); 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); }
/// <summary> /// Constructs a new <see cref="MatroskaInfoReader"/>. /// </summary> /// <param name="lfsra"> /// <see cref="ILocalFsResourceAccessor"/> pointing to the MKV file to extract information from; The caller /// is responsible that it is valid while this class is used and that it is disposed afterwards. /// </param> public MatroskaInfoReader(ILocalFsResourceAccessor lfsra) { _lfsra = lfsra; _mkvInfoPath = FileUtils.BuildAssemblyRelativePath("mkvinfo.exe"); _mkvExtractPath = FileUtils.BuildAssemblyRelativePath("mkvextract.exe"); }
public static async Task <bool> ExtractFromTagsAsync(ILocalFsResourceAccessor folderOrFileLfsra, MovieInfo movieInfo) { // Calling EnsureLocalFileSystemAccess not necessary; only string operation string extensionLower = StringUtils.TrimToEmpty(Path.GetExtension(folderOrFileLfsra.LocalFileSystemPath)).ToLower(); if (!MatroskaConsts.MATROSKA_VIDEO_EXTENSIONS.Contains(extensionLower)) { return(false); } // Try to get extended information out of matroska files) MatroskaBinaryReader mkvReader = new MatroskaBinaryReader(folderOrFileLfsra); // Add keys to be extracted to tags dictionary, matching results will returned as value Dictionary <string, IList <string> > tagsToExtract = MatroskaConsts.DefaultVideoTags; await mkvReader.ReadTagsAsync(tagsToExtract).ConfigureAwait(false); // Read plot IList <string> tags = tagsToExtract[MatroskaConsts.TAG_EPISODE_SUMMARY]; string plot = tags != null?tags.FirstOrDefault() : string.Empty; if (!string.IsNullOrEmpty(plot)) { movieInfo.HasChanged |= MetadataUpdater.SetOrUpdateString(ref movieInfo.Summary, plot, true); } // Read genre tags = tagsToExtract[MatroskaConsts.TAG_SERIES_GENRE]; if (tags != null) { List <GenreInfo> genreList = tags.Where(s => !string.IsNullOrEmpty(s?.Trim())).Select(s => new GenreInfo { Name = s.Trim() }).ToList(); movieInfo.HasChanged |= MetadataUpdater.SetOrUpdateList(movieInfo.Genres, genreList, movieInfo.Genres.Count == 0); } // Read actors tags = tagsToExtract[MatroskaConsts.TAG_ACTORS]; if (tags != null) { movieInfo.HasChanged |= MetadataUpdater.SetOrUpdateList(movieInfo.Actors, tags.Select(t => new PersonInfo() { Name = t, Occupation = PersonAspect.OCCUPATION_ACTOR, MediaName = movieInfo.MovieName.Text }).ToList(), false); } tags = tagsToExtract[MatroskaConsts.TAG_DIRECTORS]; if (tags != null) { movieInfo.HasChanged |= MetadataUpdater.SetOrUpdateList(movieInfo.Directors, tags.Select(t => new PersonInfo() { Name = t, Occupation = PersonAspect.OCCUPATION_DIRECTOR, MediaName = movieInfo.MovieName.Text }).ToList(), false); } tags = tagsToExtract[MatroskaConsts.TAG_WRITTEN_BY]; if (tags != null) { movieInfo.HasChanged |= MetadataUpdater.SetOrUpdateList(movieInfo.Writers, tags.Select(t => new PersonInfo() { Name = t, Occupation = PersonAspect.OCCUPATION_WRITER, MediaName = movieInfo.MovieName.Text }).ToList(), false); } if (tagsToExtract[MatroskaConsts.TAG_MOVIE_IMDB_ID] != null) { string imdbId; foreach (string candidate in tagsToExtract[MatroskaConsts.TAG_MOVIE_IMDB_ID]) { if (ImdbIdMatcher.TryMatchImdbId(candidate, out imdbId)) { movieInfo.HasChanged |= MetadataUpdater.SetOrUpdateId(ref movieInfo.ImdbId, imdbId); break; } } } if (tagsToExtract[MatroskaConsts.TAG_MOVIE_TMDB_ID] != null) { int tmp; foreach (string candidate in tagsToExtract[MatroskaConsts.TAG_MOVIE_TMDB_ID]) { if (int.TryParse(candidate, out tmp) == true) { movieInfo.HasChanged |= MetadataUpdater.SetOrUpdateId(ref movieInfo.MovieDbId, tmp); break; } } } return(true); }
protected void ExtractMp4Tags(ILocalFsResourceAccessor lfsra, IDictionary<Guid, MediaItemAspect> extractedAspectData, bool forceQuickMode) { // Calling EnsureLocalFileSystemAccess not necessary; only string operation string extensionUpper = StringUtils.TrimToEmpty(Path.GetExtension(lfsra.LocalFileSystemPath)).ToUpper(); // Try to get extended information out of MP4 files) if (extensionUpper != ".MP4") return; using (lfsra.EnsureLocalFileSystemAccess()) { TagLib.File mp4File = TagLib.File.Create(lfsra.LocalFileSystemPath); if (ReferenceEquals(mp4File, null) || ReferenceEquals(mp4File.Tag, null)) return; TagLib.Tag tag = mp4File.Tag; string title = tag.Title; if (!string.IsNullOrEmpty(title)) MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, title); int year = (int)tag.Year; if (year != 0) MediaItemAspect.SetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, new DateTime(year, 1, 1)); if (!ReferenceEquals(tag.Genres, null) && tag.Genres.Length > 0) MediaItemAspect.SetCollectionAttribute(extractedAspectData, VideoAspect.ATTR_GENRES, tag.Genres); if (!ReferenceEquals(tag.Performers, null) && tag.Performers.Length > 0) MediaItemAspect.SetCollectionAttribute(extractedAspectData, VideoAspect.ATTR_ACTORS, tag.Performers); } }
/// <summary> /// Constructs a new <see cref="MatroskaInfoReader"/>. /// </summary> /// <param name="lfsra"> /// <see cref="ILocalFsResourceAccessor"/> pointing to the MKV file to extract information from; The caller /// is responsible that it is valid while this class is used and that it is disposed afterwards. /// </param> public MatroskaInfoReader(ILocalFsResourceAccessor lfsra) { _lfsra = lfsra; _mkvInfoPath = FileUtils.BuildAssemblyRelativePath("mkvinfo.exe"); _mkvExtractPath = FileUtils.BuildAssemblyRelativePath("mkvextract.exe"); }
private bool ExtractThumbnail(ILocalFsResourceAccessor lfsra, IDictionary<Guid, MediaItemAspect> extractedAspectData) { // We can only work on files and make sure this file was detected by a lower MDE before (title is set then). string title; if (!lfsra.IsFile || !MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, out title)) return false; byte[] thumb; // We only want to create missing thumbnails here, so check for existing ones first if (MediaItemAspect.TryGetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, out thumb) && thumb != null) return true; // Check for a reasonable time offset long defaultVideoOffset = 720; long videoDuration; if (MediaItemAspect.TryGetAttribute(extractedAspectData, VideoAspect.ATTR_DURATION, out videoDuration)) { if (defaultVideoOffset > videoDuration * 1 / 3) defaultVideoOffset = videoDuration * 1 / 3; } string tempFileName = Path.ChangeExtension(Path.GetTempFileName(), ".jpg"); string executable = FileUtils.BuildAssemblyRelativePath("ffmpeg.exe"); string arguments = string.Format("-ss {0} -itsoffset -5 -i \"{1}\" -vframes 1 -vf \"yadif='mode=send_frame:parity=auto:deint=all',scale=iw*sar:ih,setsar=1:1,scale=iw/2:-1\" -y \"{2}\"", defaultVideoOffset, lfsra.LocalFileSystemPath, tempFileName); try { if (ProcessUtils.TryExecute_AutoImpersonate(executable, arguments, ProcessPriorityClass.Idle, PROCESS_TIMEOUT_MS) && File.Exists(tempFileName)) { var binary = FileUtils.ReadFile(tempFileName); MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, binary); ServiceRegistration.Get<ILogger>().Info("VideoThumbnailer: Successfully created thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } else ServiceRegistration.Get<ILogger>().Warn("VideoThumbnailer: Failed to create thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } finally { if (File.Exists(tempFileName)) File.Delete(tempFileName); } return true; }
/// <summary> /// Extracts a frame image and caches them in the <see cref="IFanArtCache"/> service. /// </summary> /// <param name="lfsra"><see cref="ILocalFsResourceAccessor>"/> for the file.</param> /// <param name="mediaItemId">Id of the media item.</param> /// <param name="title">Title of the media item.</param> /// <returns><see cref="Task"/> that completes when the images have been cached.</returns> protected async Task ExtractThumbnailFanArt(ILocalFsResourceAccessor lfsra, Guid mediaItemId, string title, IDictionary <Guid, IList <MediaItemAspect> > aspects) { IFanArtCache fanArtCache = ServiceRegistration.Get <IFanArtCache>(); string filename = $"OpenCv.{Path.GetFileNameWithoutExtension(lfsra.LocalFileSystemPath)}"; // Check for a reasonable time offset int defaultVideoOffset = 720; long videoDuration; double width = 0; double height = 0; double downscale = 7.5; // Reduces the HD video frame size to a quarter size to around 256 IList <MultipleMediaItemAspect> videoAspects; if (MediaItemAspect.TryGetAspects(aspects, VideoStreamAspect.Metadata, out videoAspects)) { if ((videoDuration = videoAspects[0].GetAttributeValue <long>(VideoStreamAspect.ATTR_DURATION)) > 0) { if (defaultVideoOffset > videoDuration * DEFAULT_OPENCV_THUMBNAIL_OFFSET) { defaultVideoOffset = Convert.ToInt32(videoDuration * DEFAULT_OPENCV_THUMBNAIL_OFFSET); } } width = videoAspects[0].GetAttributeValue <int>(VideoStreamAspect.ATTR_WIDTH); height = videoAspects[0].GetAttributeValue <int>(VideoStreamAspect.ATTR_HEIGHT); downscale = width / 256.0; //256 is max size of large thumbnail aspect } var sw = Stopwatch.StartNew(); using (VideoCapture capture = new VideoCapture()) { capture.Open(lfsra.LocalFileSystemPath); int capturePos = defaultVideoOffset * 1000; if (capture.FrameCount > 0 && capture.Fps > 0) { var duration = capture.FrameCount / capture.Fps; if (defaultVideoOffset > duration) { capturePos = Convert.ToInt32(duration * DEFAULT_OPENCV_THUMBNAIL_OFFSET * 1000); } } if (capture.FrameWidth > 0) { downscale = capture.FrameWidth / 256.0; //256 is max size of large thumbnail aspect } capture.PosMsec = capturePos; using (var mat = capture.RetrieveMat()) { if (mat.Height > 0 && mat.Width > 0) { width = mat.Width; height = mat.Height; Logger.Debug("VideoFanArtHandler: Scaling thumbnail of size {1}x{2} for resource '{0}'", lfsra.LocalFileSystemPath, width, height); using (var scaledMat = mat.Resize(new OpenCvSharp.Size(width / downscale, height / downscale))) { var binary = scaledMat.ToBytes(); await fanArtCache.TrySaveFanArt(mediaItemId, title, FanArtTypes.Thumbnail, p => TrySaveFileImage(binary, p, filename)).ConfigureAwait(false); Logger.Debug("VideoFanArtHandler: Successfully created thumbnail for resource '{0}' ({1} ms)", lfsra.LocalFileSystemPath, sw.ElapsedMilliseconds); } } else { Logger.Warn("VideoFanArtHandler: Failed to create thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } } } }
private bool ExtractMovieData(ILocalFsResourceAccessor lfsra, IDictionary<Guid, MediaItemAspect> extractedAspectData) { // Calling EnsureLocalFileSystemAccess not necessary; only string operation string[] pathsToTest = new[] { lfsra.LocalFileSystemPath, lfsra.CanonicalLocalResourcePath.ToString() }; string title; // VideoAspect must be present to be sure it is actually a video resource. if (!extractedAspectData.ContainsKey(VideoAspect.ASPECT_ID)) return false; if (!MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, out title) || string.IsNullOrEmpty(title)) return false; MovieInfo movieInfo = new MovieInfo { MovieName = title, }; // Allow the online lookup to choose best matching language for metadata ICollection<string> movieLanguages; if (MediaItemAspect.TryGetAttribute(extractedAspectData, VideoAspect.ATTR_AUDIOLANGUAGES, out movieLanguages) && movieLanguages.Count > 0) movieInfo.Languages.AddRange(movieLanguages); // Try to use an existing IMDB id for exact mapping string imdbId; if (MediaItemAspect.TryGetAttribute(extractedAspectData, MovieAspect.ATTR_IMDB_ID, out imdbId) || pathsToTest.Any(path => ImdbIdMatcher.TryMatchImdbId(path, out imdbId)) || MatroskaMatcher.TryMatchImdbId(lfsra, out imdbId)) movieInfo.ImdbId = imdbId; // Also test the full path year, using a dummy. This is useful if the path contains the real name and year. foreach (string path in pathsToTest) { MovieInfo dummy = new MovieInfo { MovieName = path }; if (NamePreprocessor.MatchTitleYear(dummy)) { movieInfo.MovieName = dummy.MovieName; movieInfo.Year = dummy.Year; break; } } // When searching movie title, the year can be relevant for multiple titles with same name but different years DateTime recordingDate; if (MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_RECORDINGTIME, out recordingDate)) movieInfo.Year = recordingDate.Year; if (MovieTheMovieDbMatcher.Instance.FindAndUpdateMovie(movieInfo)) { if (!_onlyFanArt) movieInfo.SetMetadata(extractedAspectData); if (_onlyFanArt && movieInfo.MovieDbId > 0) MediaItemAspect.SetAttribute(extractedAspectData, MovieAspect.ATTR_TMDB_ID, movieInfo.MovieDbId); if (_onlyFanArt && movieInfo.CollectionMovieDbId > 0) MediaItemAspect.SetAttribute(extractedAspectData, MovieAspect.ATTR_COLLECTION_ID, movieInfo.CollectionMovieDbId); return true; } return false; }
private Task <bool> ExtractThumbnailAsync(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData) { // We can only work on files and make sure this file was detected by a lower MDE before (title is set then). // VideoAspect must be present to be sure it is actually a video resource. if (!lfsra.IsFile || !extractedAspectData.ContainsKey(VideoStreamAspect.ASPECT_ID)) { return(Task.FromResult(false)); } //ServiceRegistration.Get<ILogger>().Info("OCVVideoThumbnailer: Evaluate {0}", lfsra.ResourceName); bool isPrimaryResource = false; IList <MultipleMediaItemAspect> resourceAspects; if (MediaItemAspect.TryGetAspects(extractedAspectData, ProviderResourceAspect.Metadata, out resourceAspects)) { foreach (MultipleMediaItemAspect pra in resourceAspects) { string accessorPath = (string)pra.GetAttributeValue(ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH); ResourcePath resourcePath = ResourcePath.Deserialize(accessorPath); if (resourcePath.Equals(lfsra.CanonicalLocalResourcePath)) { if (pra.GetAttributeValue <int?>(ProviderResourceAspect.ATTR_TYPE) == ProviderResourceAspect.TYPE_PRIMARY) { isPrimaryResource = true; break; } } } } if (!isPrimaryResource) //Ignore subtitles { return(Task.FromResult(false)); } // Check for a reasonable time offset int defaultVideoOffset = 720; long videoDuration; double width = 0; double height = 0; double downscale = 2; // Reduces the video frame size to a half of original IList <MultipleMediaItemAspect> videoAspects; if (MediaItemAspect.TryGetAspects(extractedAspectData, VideoStreamAspect.Metadata, out videoAspects)) { if ((videoDuration = videoAspects[0].GetAttributeValue <long>(VideoStreamAspect.ATTR_DURATION)) > 0) { if (defaultVideoOffset > videoDuration * DEFAULT_THUMBNAIL_OFFSET) { defaultVideoOffset = Convert.ToInt32(videoDuration * DEFAULT_THUMBNAIL_OFFSET); } } width = videoAspects[0].GetAttributeValue <int>(VideoStreamAspect.ATTR_WIDTH); height = videoAspects[0].GetAttributeValue <int>(VideoStreamAspect.ATTR_HEIGHT); downscale = width / 256.0; //256 is max size of large thumbnail aspect } using (lfsra.EnsureLocalFileSystemAccess()) { using (VideoCapture capture = new VideoCapture()) { capture.Open(lfsra.LocalFileSystemPath); int capturePos = defaultVideoOffset * 1000; if (capture.FrameCount > 0 && capture.Fps > 0) { var duration = capture.FrameCount / capture.Fps; if (defaultVideoOffset > duration) { capturePos = Convert.ToInt32(duration * DEFAULT_THUMBNAIL_OFFSET * 1000); } } if (capture.FrameWidth > 0 && width == 0) { width = capture.FrameWidth; downscale = width / 256.0; //256 is max size of large thumbnail aspect } capture.PosMsec = capturePos; using (var mat = capture.RetrieveMat()) { if (mat.Height > 0 && mat.Width > 0) { width = mat.Width; height = mat.Height; ServiceRegistration.Get <ILogger>().Debug("OCVVideoThumbnailer: Scaling thumbnail of size {1}x{2} for resource '{0}'", lfsra.LocalFileSystemPath, width, height); using (var scaledMat = mat.Resize(new OpenCvSharp.Size(width / downscale, height / downscale))) { var binary = scaledMat.ToBytes(); MediaItemAspect.SetAttribute(extractedAspectData, ThumbnailLargeAspect.ATTR_THUMBNAIL, binary); ServiceRegistration.Get <ILogger>().Info("OCVVideoThumbnailer: Successfully created thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } } else { ServiceRegistration.Get <ILogger>().Warn("OCVVideoThumbnailer: Failed to create thumbnail for resource '{0}'", lfsra.LocalFileSystemPath); } } } } return(Task.FromResult(true)); }
/// <summary> /// Executes FFProbe and ensures that it has access to the respective resource /// </summary> /// <param name="lfsra"><see cref="ILocalFsResourceAccessor"/> to which FFProbe needs access to</param> /// <param name="arguments">Arguments for FFProbe</param> /// <param name="priorityClass">Process priority</param> /// <param name="maxWaitMs">Maximum time to wait for completion</param> /// <returns>A <see cref="Task"/> representing the result of executing FFProbe</returns> /// <remarks> /// This is a convenience method that enables executing FFProbe directly on the <see cref="ILocalFsResourceAccessor"/> /// interface to which FFProbe needs access. The purpose of an <see cref="ILocalFsResourceAccessor"/> is providing /// access to a resource - not executing programs which is why this method is implemented as an extension method instead of /// a method directly on the interface. /// </remarks> public static Task <ProcessExecutionResult> FFProbeExecuteWithResourceAccessAsync(ILocalFsResourceAccessor lfsra, string arguments, ProcessPriorityClass priorityClass, int maxWaitMs) { return(lfsra.ExecuteWithResourceAccessAsync(_ffProbeBinPath, arguments, priorityClass, maxWaitMs)); }
/// <summary> /// Gets a list of <see cref="FanArtImage"/>s for a requested <paramref name="mediaType"/>, <paramref name="fanArtType"/> and <paramref name="name"/>. /// The name can be: Series name, Actor name, Artist name depending on the <paramref name="mediaType"/>. /// </summary> /// <param name="mediaType">Requested FanArtMediaType</param> /// <param name="fanArtType">Requested FanArtType</param> /// <param name="name">Requested name of Series, Actor, Artist...</param> /// <param name="maxWidth">Maximum width for image. <c>0</c> returns image in original size.</param> /// <param name="maxHeight">Maximum height for image. <c>0</c> returns image in original size.</param> /// <param name="singleRandom">If <c>true</c> only one random image URI will be returned</param> /// <param name="result">Result if return code is <c>true</c>.</param> /// <returns><c>true</c> if at least one match was found.</returns> public bool TryGetFanArt(FanArtConstants.FanArtMediaType mediaType, FanArtConstants.FanArtType fanArtType, string name, int maxWidth, int maxHeight, bool singleRandom, out IList <IResourceLocator> result) { result = null; Guid mediaItemId; // Don't try to load "fanart" for images if (!Guid.TryParse(name, out mediaItemId) || mediaType == FanArtConstants.FanArtMediaType.Image) { 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(_necessarryMIAs, filter), false); if (items == null || items.Count == 0) { return(false); } MediaItem mediaItem = items.First(); string fileSystemPath = null; List <string> localPatterns = new List <string>(); List <IResourceLocator> files = new List <IResourceLocator>(); // File based access try { var resourceLocator = mediaItem.GetResourceLocator(); using (var accessor = resourceLocator.CreateAccessor()) { ILocalFsResourceAccessor fsra = accessor as ILocalFsResourceAccessor; if (fsra != null) { fileSystemPath = fsra.LocalFileSystemPath; var path = Path.GetDirectoryName(fileSystemPath); var file = Path.GetFileNameWithoutExtension(fileSystemPath); if (fanArtType == FanArtConstants.FanArtType.Poster || fanArtType == FanArtConstants.FanArtType.Thumbnail) { localPatterns.Add(Path.ChangeExtension(fileSystemPath, ".jpg")); localPatterns.Add(Path.Combine(path, file + "-poster.jpg")); localPatterns.Add(Path.Combine(path, "folder.jpg")); } if (fanArtType == FanArtConstants.FanArtType.FanArt) { localPatterns.Add(Path.Combine(path, "backdrop.jpg")); localPatterns.Add(Path.Combine(path, file + "-fanart*.jpg")); localPatterns.Add(Path.Combine(path, "ExtraFanArt\\*.jpg")); } // Copy all patterns for .jpg -> .png + .tbn localPatterns.AddRange(new List <string>(localPatterns).Select(p => p.Replace(".jpg", ".png"))); localPatterns.AddRange(new List <string>(localPatterns).Select(p => p.Replace(".jpg", ".tbn"))); foreach (var pattern in localPatterns) { try { var pathPart = Path.GetDirectoryName(pattern); var filePart = Path.GetFileName(pattern); DirectoryInfo directoryInfo = new DirectoryInfo(pathPart); if (directoryInfo.Exists) { files.AddRange(directoryInfo.GetFiles(filePart) .Select(f => f.FullName) .Select(fileName => new ResourceLocator(resourceLocator.NativeSystemId, ResourcePath.BuildBaseProviderPath(resourceLocator.NativeResourcePath.LastPathSegment.ProviderId, fileName)))); } } catch { } } } } } catch (Exception ex) { #if DEBUG ServiceRegistration.Get <ILogger>().Warn("Error while search fanart of type '{0}' for path '{1}'", ex, fanArtType, fileSystemPath); #endif } result = files; return(files.Count > 0); }
/// <summary> /// Constructs a new <see cref="MatroskaBinaryReader"/>. /// </summary> /// <param name="lfsra"> /// <see cref="ILocalFsResourceAccessor"/> pointing to the MKV file to extract information from; The caller /// is responsible that it is valid while this class is used and that it is disposed afterwards. /// </param> public MatroskaBinaryReader(ILocalFsResourceAccessor lfsra) { _lfsra = lfsra; _descriptorsMap = _elementDescriptors.Where(d => d != null).ToDictionary(d => d.Identifier.EncodedValue, d => d); }
protected bool ExtractSeriesData(ILocalFsResourceAccessor lfsra, IDictionary<Guid, MediaItemAspect> extractedAspectData) { // VideoAspect must be present to be sure it is actually a video resource. if (!extractedAspectData.ContainsKey(VideoAspect.ASPECT_ID)) return false; SeriesInfo seriesInfo = null; // First check if we already have a complete match from a previous MDE string title; int tvDbId; int seasonNumber; IEnumerable<int> episodeNumbers; if (MediaItemAspect.TryGetAttribute(extractedAspectData, MediaAspect.ATTR_TITLE, out title) && MediaItemAspect.TryGetAttribute(extractedAspectData, SeriesAspect.ATTR_TVDB_ID, out tvDbId) && MediaItemAspect.TryGetAttribute(extractedAspectData, SeriesAspect.ATTR_SEASON, out seasonNumber) && (episodeNumbers = extractedAspectData[SeriesAspect.ASPECT_ID].GetCollectionAttribute<int>(SeriesAspect.ATTR_EPISODE)) != null) { seriesInfo = new SeriesInfo { Series = title, TvdbId = tvDbId, SeasonNumber = seasonNumber, }; episodeNumbers.ToList().ForEach(n => seriesInfo.EpisodeNumbers.Add(n)); } // If there was no complete match, yet, try to get extended information out of matroska files) if (seriesInfo == null || !seriesInfo.IsCompleteMatch) { MatroskaMatcher matroskaMatcher = new MatroskaMatcher(); if (matroskaMatcher.MatchSeries(lfsra, out seriesInfo, ref extractedAspectData)) { ServiceRegistration.Get<ILogger>().Debug("ExtractSeriesData: Found SeriesInformation by MatroskaMatcher for {0}, IMDB {1}, TVDB {2}, IsCompleteMatch {3}", seriesInfo.Series, seriesInfo.ImdbId, seriesInfo.TvdbId, seriesInfo.IsCompleteMatch); } } // If no information was found before, try name matching if (seriesInfo == null || !seriesInfo.IsCompleteMatch) { // Try to match series from folder and file namings SeriesMatcher seriesMatcher = new SeriesMatcher(); seriesMatcher.MatchSeries(lfsra, out seriesInfo); } // Lookup online information (incl. fanart) if (seriesInfo != null && seriesInfo.IsCompleteMatch) { SeriesTvDbMatcher.Instance.FindAndUpdateSeries(seriesInfo); if (!_onlyFanArt) seriesInfo.SetMetadata(extractedAspectData); } return (seriesInfo != null && seriesInfo.IsCompleteMatch && !_onlyFanArt); }