protected int FindExternalSubtitles(ILocalFsResourceAccessor lfsra, IDictionary <Guid, IList <MediaItemAspect> > extractedAspectData) { int subtitleCount = 0; try { IList <MultipleMediaItemAspect> providerResourceAspects; if (!MediaItemAspect.TryGetAspects(extractedAspectData, ProviderResourceAspect.Metadata, out providerResourceAspects)) { return(0); } int newResourceIndex = -1; foreach (MultipleMediaItemAspect providerResourceAspect in providerResourceAspects) { int resouceIndex = providerResourceAspect.GetAttributeValue <int>(ProviderResourceAspect.ATTR_RESOURCE_INDEX); if (newResourceIndex < resouceIndex) { newResourceIndex = resouceIndex; } } newResourceIndex++; using (lfsra.EnsureLocalFileSystemAccess()) { foreach (MultipleMediaItemAspect mmia in providerResourceAspects) { string accessorPath = (string)mmia.GetAttributeValue(ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH); ResourcePath resourcePath = ResourcePath.Deserialize(accessorPath); if (mmia.GetAttributeValue <int>(ProviderResourceAspect.ATTR_TYPE) != ProviderResourceAspect.TYPE_PRIMARY) { continue; } string filePath = LocalFsResourceProviderBase.ToDosPath(resourcePath); if (string.IsNullOrEmpty(filePath)) { continue; } List <string> subs = new List <string>(); int videoResouceIndex = (int)mmia.GetAttributeValue(ProviderResourceAspect.ATTR_RESOURCE_INDEX); string[] subFiles = Directory.GetFiles(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath) + "*.*"); if (subFiles != null) { subs.AddRange(subFiles); } foreach (string folder in SUBTITLE_FOLDERS) { if (string.IsNullOrEmpty(Path.GetPathRoot(folder)) && Directory.Exists(Path.Combine(Path.GetDirectoryName(filePath), folder))) //Is relative path { subFiles = Directory.GetFiles(Path.Combine(Path.GetDirectoryName(filePath), folder), Path.GetFileNameWithoutExtension(filePath) + "*.*"); } else if (Directory.Exists(folder)) //Is absolute path { subFiles = Directory.GetFiles(folder, Path.GetFileNameWithoutExtension(filePath) + "*.*"); } if (subFiles != null) { subs.AddRange(subFiles); } } foreach (string subFile in subFiles) { if (!HasSubtitleExtension(subFile)) { continue; } LocalFsResourceAccessor fsra = new LocalFsResourceAccessor((LocalFsResourceProvider)lfsra.ParentProvider, LocalFsResourceProviderBase.ToProviderPath(subFile)); //Check if already exists bool exists = false; foreach (MultipleMediaItemAspect providerResourceAspect in providerResourceAspects) { string subAccessorPath = (string)providerResourceAspect.GetAttributeValue(ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH); ResourcePath subResourcePath = ResourcePath.Deserialize(subAccessorPath); if (subResourcePath.Equals(fsra.CanonicalLocalResourcePath)) { //Already exists exists = true; break; } } if (exists) { continue; } string subFormat = GetSubtitleFormat(subFile); if (!string.IsNullOrEmpty(subFormat)) { MultipleMediaItemAspect providerResourceAspect = MediaItemAspect.CreateAspect(extractedAspectData, ProviderResourceAspect.Metadata); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_RESOURCE_INDEX, newResourceIndex); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_TYPE, ProviderResourceAspect.TYPE_SECONDARY); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_MIME_TYPE, GetSubtitleMime(subFormat)); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_SIZE, fsra.Size); providerResourceAspect.SetAttribute(ProviderResourceAspect.ATTR_RESOURCE_ACCESSOR_PATH, fsra.CanonicalLocalResourcePath.Serialize()); MultipleMediaItemAspect subtitleResourceAspect = MediaItemAspect.CreateAspect(extractedAspectData, SubtitleAspect.Metadata); subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_RESOURCE_INDEX, newResourceIndex); subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_VIDEO_RESOURCE_INDEX, videoResouceIndex); subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_STREAM_INDEX, -1); //External subtitle subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_SUBTITLE_FORMAT, subFormat); subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_INTERNAL, false); subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_DEFAULT, subFile.ToLowerInvariant().Contains(".default.")); subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_FORCED, subFile.ToLowerInvariant().Contains(".forced.")); bool imageBased = IsImageBasedSubtitle(subFormat); string language = GetSubtitleLanguage(subFile, imageBased); if (language != null) { subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_SUBTITLE_LANGUAGE, language); } if (imageBased == false) { string encoding = GetSubtitleEncoding(subFile, language); if (encoding != null) { subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_SUBTITLE_ENCODING, encoding); } } else { subtitleResourceAspect.SetAttribute(SubtitleAspect.ATTR_SUBTITLE_ENCODING, SubtitleAspect.BINARY_ENCODING); } newResourceIndex++; subtitleCount++; } } } } } catch (Exception e) { ServiceRegistration.Get <ILogger>().Info("SubtitleMetadataExtractor: Exception finding external subtitles for resource '{0}' (Text: '{1}')", e, lfsra.CanonicalLocalResourcePath, e.Message); } return(subtitleCount); }
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); }
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 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 * 1 / 3) { defaultVideoOffset = Convert.ToInt32(videoDuration * 1 / 3); } } double width = videoAspects[0].GetAttributeValue <int>(VideoStreamAspect.ATTR_WIDTH); double 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); capture.PosMsec = defaultVideoOffset * 1000; using (var mat = capture.RetrieveMat()) { if (mat.Height > 0 && mat.Width > 0) { double width = mat.Width; double 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)); }
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); }