Esempio n. 1
0
        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);
        }