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); }
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); }
protected override async Task <bool> ConvertSubtitleFileAsync(string clientId, VideoTranscoding video, double timeStart, string transcodingFile, SubtitleStream sourceSubtitle, SubtitleStream res) { SubtitleCodec targetCodec = video.TargetSubtitleCodec; if (targetCodec == SubtitleCodec.Unknown) { targetCodec = sourceSubtitle.Codec; } string tempFile = null; FFMpegTranscodeData data = new FFMpegTranscodeData(_cachePath) { TranscodeId = video.TranscodeId + "_sub", ClientId = clientId }; if (string.IsNullOrEmpty(video.TranscoderBinPath) == false) { data.TranscoderBinPath = video.TranscoderBinPath; } if (string.IsNullOrEmpty(video.TranscoderArguments) == false) { // TODO: not sure if this is working data.TranscoderArguments = video.TranscoderArguments; data.InputMediaFilePaths.Add(0, res.SourcePath); data.InputArguments.Add(0, new List <string>()); } else { tempFile = transcodingFile + ".tmp"; res = await ConvertSubtitleEncodingAsync(res, tempFile, video.TargetSubtitleCharacterEncoding).ConfigureAwait(false); // TODO: not sure if this is working _ffMpegCommandline.InitTranscodingParameters(false, new Dictionary <int, string> { { 0, res.SourcePath } }, ref data); data.InputArguments[0].Add(string.Format("-f {0}", FFMpegGetSubtitleContainer.GetSubtitleContainer(sourceSubtitle.Codec))); if (timeStart > 0) { data.OutputArguments.Add(string.Format(CultureInfo.InvariantCulture, "-ss {0:0.0}", timeStart)); } res.Codec = targetCodec; string subtitleEncoder = "copy"; if (res.Codec == SubtitleCodec.Unknown) { res.Codec = SubtitleCodec.Ass; } if (sourceSubtitle.Codec != res.Codec) { subtitleEncoder = FFMpegGetSubtitleContainer.GetSubtitleContainer(res.Codec); } string subtitleFormat = FFMpegGetSubtitleContainer.GetSubtitleContainer(res.Codec); data.OutputArguments.Add("-vn"); data.OutputArguments.Add("-an"); data.OutputArguments.Add(string.Format("-c:s {0}", subtitleEncoder)); data.OutputArguments.Add(string.Format("-f {0}", subtitleFormat)); } data.OutputFilePath = transcodingFile; _logger.Debug("FFMpegMediaConverter: Invoking transcoder to transcode subtitle file '{0}' for transcode '{1}'", res.SourcePath, data.TranscodeId); bool success = false; var path = ResourcePath.Deserialize(res.SourcePath); if (path.TryCreateLocalResourceAccessor(out var subRes)) { using (var rah = new LocalFsResourceAccessorHelper(subRes)) using (var access = rah.LocalFsResourceAccessor.EnsureLocalFileSystemAccess()) { var result = await FFMpegBinary.FFMpegExecuteWithResourceAccessAsync(rah.LocalFsResourceAccessor, data.TranscoderArguments, ProcessPriorityClass.Normal, _transcoderTimeout).ConfigureAwait(false); success = result.Success; } } if (success && File.Exists(transcodingFile) == true) { if (tempFile != null && File.Exists(tempFile)) { File.Delete(tempFile); } res.SourcePath = LocalFsResourceProviderBase.ToProviderPath(transcodingFile); return(true); } return(false); }