コード例 #1
0
        private async void TranscodeProcessor(object args)
        {
            FFMpegTranscodeThreadData data = (FFMpegTranscodeThreadData)args;
            bool isStream = data.Context.Live && !data.Context.Segmented;
            bool isFile   = true;
            bool isSlimTv = false;

            try
            {
                if (data.Context.Segmented == true)
                {
                    await FFMpegPlaylistManifest.CreatePlaylistFilesAsync(data.TranscodeData).ConfigureAwait(false);
                }

                int  liveChannelId = 0;
                bool runProcess    = true;
                using (IResourceAccessor mediaAccessor = data.TranscodeData.GetFirstResourceAccessor())
                {
                    if (mediaAccessor is ITranscodeLiveAccessor tla)
                    {
                        isSlimTv      = true;
                        liveChannelId = tla.ChannelId;
                    }
                }

                data.Context.Start();
                data.TranscodeData.ClearRuntimeResourcePaths();
                int exitCode = -1;

                string identifier = "Transcode_" + data.TranscodeData.ClientId;
                if (isSlimTv)
                {
                    var result = await _slimtTvHandler.StartTuningAsync(identifier, liveChannelId).ConfigureAwait(false);

                    if (!result.Success)
                    {
                        _logger.Error("FFMpegMediaConverter: Transcoder unable to start timeshifting for channel {0}", liveChannelId);
                        runProcess = false;
                        exitCode   = 5000;
                    }
                    else
                    {
                        using (var mediaAccessor = await _slimtTvHandler.GetDefaultAccessorAsync(liveChannelId).ConfigureAwait(false))
                        {
                            if (mediaAccessor is INetworkResourceAccessor)
                            {
                                int mediaStreamIndex = data.TranscodeData.FirstResourceIndex;
                                data.TranscodeData.AddRuntimeResourcePath(ResourcePath.Deserialize(data.TranscodeData.InputMediaFilePaths[mediaStreamIndex]), mediaAccessor.CanonicalLocalResourcePath.Serialize());
                            }
                            else
                            {
                                _logger.Error("FFMpegMediaConverter: Transcoder unable to start timeshifting for channel {0} because no URL was found", liveChannelId);
                                runProcess = false;
                                exitCode   = 5001;
                            }
                        }
                    }
                }

                ResourcePath firstPath;
                using (var mediaAccessor = data.TranscodeData.GetFirstResourceAccessor())
                {
                    firstPath = mediaAccessor.CanonicalLocalResourcePath;
                    if (mediaAccessor is INetworkResourceAccessor)
                    {
                        isFile = false;
                    }
                }

                if (runProcess)
                {
                    //Prepare resources
                    List <IDisposable> disposables = new List <IDisposable>();
                    foreach (var res in data.TranscodeData.GetResourceAccessors())
                    {
                        if (!(res is INetworkResourceAccessor))
                        {
                            var rah = new LocalFsResourceAccessorHelper(res);
                            data.TranscodeData.AddRuntimeResourcePath(res.CanonicalLocalResourcePath, rah.LocalFsResourceAccessor.LocalFileSystemPath);
                            var accessToken = rah.LocalFsResourceAccessor.EnsureLocalFileSystemAccess();
                            if (accessToken != null)
                            {
                                disposables.Add(accessToken);
                            }
                            disposables.Add(rah);
                        }

                        disposables.Add(res);
                    }

                    ProcessStartInfo startInfo = new ProcessStartInfo()
                    {
                        FileName               = data.TranscodeData.TranscoderBinPath,
                        WorkingDirectory       = data.TranscodeData.WorkPath,
                        Arguments              = data.TranscodeData.TranscoderArguments,
                        UseShellExecute        = false,
                        CreateNoWindow         = true,
                        RedirectStandardOutput = true,
                        RedirectStandardError  = true,
                        RedirectStandardInput  = true,
                        StandardOutputEncoding = Encoding.UTF8,
                        StandardErrorEncoding  = Encoding.UTF8
                    };

                    _logger.Debug("FFMpegMediaConverter: Transcoder '{0}' invoked with command line arguments '{1}'", data.TranscodeData.TranscoderBinPath, data.TranscodeData.TranscoderArguments);

                    IntPtr userToken = IntPtr.Zero;
                    try
                    {
                        //TODO: Fix usages of obsolete and deprecated methods when alternative is available
#if !TRANSCODE_CONSOLE_TEST
                        using (ServiceRegistration.Get <IImpersonationService>().CheckImpersonationFor(firstPath))
                        {
                            //Only when the server is running as a service it will have elevation rights
                            using (ImpersonationProcess ffmpeg = new ImpersonationProcess {
                                StartInfo = startInfo
                            })
                            {
                                if (isFile && !ImpersonationHelper.GetTokenByProcess(out userToken, true))
                                {
                                    return;
                                }
#else
                        {
                            using (Process ffmpeg = new Process()
                            {
                                StartInfo = startInfo
                            })
                            {
#endif
                                ffmpeg.EnableRaisingEvents = true; //Enable raising events because Process does not raise events by default.
                                if (isStream == false)
                                {
                                    ffmpeg.OutputDataReceived += data.Context.OutputDataReceived;
                                }

                                ffmpeg.ErrorDataReceived += data.Context.ErrorDataReceived;
#if !TRANSCODE_CONSOLE_TEST
                                if (isFile)
                                {
                                    ffmpeg.StartAsUser(userToken);
                                }
                                else
                                {
                                    ffmpeg.Start();
                                }
#else
                                ffmpeg.Start();
#endif
                                ffmpeg.BeginErrorReadLine();
                                if (isStream == false)
                                {
                                    ffmpeg.BeginOutputReadLine();
                                }
                                else
                                {
                                    data.TranscodeData.LiveStream = ffmpeg.StandardOutput.BaseStream;
                                }

                                RunTranscodingProcess(ffmpeg, data, isStream);

                                ffmpeg.WaitForExit();
                                exitCode = ffmpeg.ExitCode;
                                ffmpeg.Close();
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        if (isStream || data.TranscodeData.OutputFilePath == null)
                        {
                            _logger.Error("FFMpegMediaConverter: Transcoder command failed for stream '{0}'", ex, data.TranscodeData.TranscodeId);
                        }
                        else
                        {
                            _logger.Error("FFMpegMediaConverter: Transcoder command failed for file '{0}'", ex, data.TranscodeData.OutputFilePath);
                        }

                        data.Context.Fail();
                    }
                    finally
                    {
#if !TRANSCODE_CONSOLE_TEST
                        if (userToken != IntPtr.Zero)
                        {
                            NativeMethods.CloseHandle(userToken);
                        }
#endif
                        data.TranscodeData.LiveStream?.Dispose();
                        foreach (var disposable in disposables)
                        {
                            disposable?.Dispose();
                        }
                    }
                }

                if (exitCode > 0)
                {
                    data.Context.Fail();
                }
                else
                {
                    data.Context.Stop();
                }
                _ffMpegEncoderHandler.EndEncoding(data.TranscodeData.Encoder, data.TranscodeData.TranscodeId);

                if (isSlimTv)
                {
                    if (await _slimtTvHandler.EndTuningAsync(identifier).ConfigureAwait(false) == false)
                    {
                        _logger.Error("FFMpegMediaConverter: Transcoder unable to stop timeshifting for channel {0}", liveChannelId);
                    }
                }

                string filePath = data.Context.TargetFile;
                bool   isFolder = false;
                if (string.IsNullOrEmpty(data.Context.SegmentDir) == false)
                {
                    filePath = data.Context.SegmentDir;
                    isFolder = true;
                }

                if (exitCode > 0 || data.Context.Aborted)
                {
                    if (exitCode > 0)
                    {
                        if (isStream || data.TranscodeData.OutputFilePath == null)
                        {
                            _logger.Debug("FFMpegMediaConverter: Transcoder command failed with error {1} for stream '{0}'", data.TranscodeData.TranscodeId, exitCode);
                        }
                        else
                        {
                            _logger.Debug("FFMpegMediaConverter: Transcoder command failed with error {1} for file '{0}'", data.TranscodeData.OutputFilePath, exitCode);
                        }
                    }

                    if (data.Context.Aborted)
                    {
                        if (isStream || data.TranscodeData.OutputFilePath == null)
                        {
                            _logger.Debug("FFMpegMediaConverter: Transcoder command aborted for stream '{0}'", data.TranscodeData.TranscodeId);
                        }
                        else
                        {
                            _logger.Debug("FFMpegMediaConverter: Transcoder command aborted for file '{0}'", data.TranscodeData.OutputFilePath);
                        }
                    }
                    else
                    {
                        _logger.Debug("FFMpegMediaConverter: FFMpeg error \n {0}", data.Context.ConsoleErrorOutput);
                    }

                    data.Context.DeleteFiles();
                }
                else
                {
                    //Touch cache files so they will not be cleaned up
                    if (isFolder == false)
                    {
                        TouchFile(filePath);
                    }
                    else
                    {
                        TouchDirectory(filePath);
                    }
                }
            }
            catch (Exception ex)
            {
                if (isStream || data.TranscodeData.OutputFilePath == null)
                {
                    _logger.Error("FFMpegMediaConverter: Transcoder failed processing '{0}'", ex, data.TranscodeData.TranscodeId);
                }
                else
                {
                    _logger.Error("FFMpegMediaConverter: Transcoder failed processing file '{0}'", ex, data.TranscodeData.OutputFilePath);
                }
            }
            finally
            {
                await RemoveTranscodeContextAsync(data.TranscodeData.ClientId, data.TranscodeData.TranscodeId, data.Context).ConfigureAwait(false);
            }
        }
コード例 #2
0
        protected override async Task <TranscodeContext> TranscodeVideoAsync(string clientId, VideoTranscoding video, double timeStart, double timeDuration, bool waitForBuffer)
        {
            FFMpegTranscodeContext context = new FFMpegTranscodeContext(_cacheEnabled, _cachePath);

            context.TargetDuration = video.SourceMediaDuration;
            if (timeStart == 0 && video.TargetIsLive == false && _cacheEnabled)
            {
                timeDuration    = 0;
                context.Partial = false;
            }
            else if (video.TargetVideoContainer == VideoContainer.Hls)
            {
                context.Partial = true;
            }
            else
            {
                video.TargetIsLive = true;
                context.Partial    = true;
            }
            if (video.TargetVideoContainer == VideoContainer.Unknown)
            {
                video.TargetVideoContainer = video.SourceVideoContainer;
            }

            bool          embeddedSupported = false;
            SubtitleCodec embeddedSubCodec  = SubtitleCodec.Unknown;

            if (video.TargetSubtitleSupport == SubtitleSupport.Embedded)
            {
                if (video.TargetVideoContainer == VideoContainer.Matroska)
                {
                    embeddedSupported         = true;
                    embeddedSubCodec          = SubtitleCodec.Ass;
                    video.TargetSubtitleCodec = SubtitleCodec.Ass;
                }
                else if (video.TargetVideoContainer == VideoContainer.Mp4)
                {
                    embeddedSupported         = true;
                    embeddedSubCodec          = SubtitleCodec.MovTxt;
                    video.TargetSubtitleCodec = SubtitleCodec.MovTxt;
                }
                else if (video.TargetVideoContainer == VideoContainer.Hls)
                {
                    embeddedSupported         = true;
                    embeddedSubCodec          = SubtitleCodec.WebVtt;
                    video.TargetSubtitleCodec = SubtitleCodec.WebVtt;
                }
                else if (video.TargetVideoContainer == VideoContainer.Avi)
                {
                    embeddedSupported         = true;
                    embeddedSubCodec          = SubtitleCodec.Srt;
                    video.TargetSubtitleCodec = SubtitleCodec.Srt;
                }
                //else if (video.TargetVideoContainer == VideoContainer.Mpeg2Ts)
                //{
                //  embeddedSupported = true;
                //  embeddedSubCodec = SubtitleCodec.DvbSub;
                //  video.TargetSubtitleCodec = SubtitleCodec.VobSub;
                //}
                else
                {
                    _logger.Debug("FFMpegMediaConverter: Container {0} does not support embedded subtitles", video.TargetVideoContainer);
                }
            }
            video.TargetSubtitleMime       = SubtitleHelper.GetSubtitleMime(video.TargetSubtitleCodec);
            video.PreferredSourceSubtitles = await GetSubtitlesAsync(clientId, video, timeStart).ConfigureAwait(false);

            string transcodingFile = GetTranscodingVideoFileName(video, timeStart, embeddedSupported);

            transcodingFile = Path.Combine(_cachePath, transcodingFile);

            if (File.Exists(transcodingFile))
            {
                //Use non-partial transcode if possible
                TranscodeContext existingContext = await GetExistingTranscodeContextAsync(clientId, video.TranscodeId).ConfigureAwait(false);

                if (existingContext != null)
                {
                    existingContext.TargetFile = transcodingFile;
                    if (existingContext.Stream == null)
                    {
                        existingContext.AssignStream(await GetFileStreamAsync(transcodingFile).ConfigureAwait(false));
                    }
                    if (existingContext.CurrentDuration.TotalSeconds == 0)
                    {
                        double bitrate = 0;
                        if (video.TargetVideoBitrate.HasValue && video.TargetAudioBitrate.HasValue)
                        {
                            bitrate = video.TargetVideoBitrate.Value + video.TargetAudioBitrate.Value;
                        }
                        else if (video.SourceVideoStream.Bitrate.HasValue && video.SourceAudioStreams.Any(a => a.Bitrate > 0))
                        {
                            bitrate = video.SourceVideoStream.Bitrate.Value + video.SourceAudioStreams.Max(a => a.Bitrate ?? 0);
                        }
                        bitrate *= 1024; //Bitrate in bits/s
                        if (bitrate > 0)
                        {
                            long startByte = Convert.ToInt64((bitrate * timeStart) / 8.0);
                            if (existingContext.Stream.Length > startByte)
                            {
                                return(existingContext);
                            }
                        }
                    }
                    else
                    {
                        if (existingContext.CurrentDuration.TotalSeconds > timeStart)
                        {
                            return(existingContext);
                        }
                    }
                }
                else
                {
                    //Presume that it is a cached file
                    TouchFile(transcodingFile);
                    context.Partial    = false;
                    context.TargetFile = transcodingFile;
                    context.AssignStream(await GetFileStreamAsync(transcodingFile).ConfigureAwait(false));
                    return(context);
                }
            }

            if (video.TargetVideoContainer == VideoContainer.Hls)
            {
                long requestedSegmentSequence = requestedSegmentSequence = Convert.ToInt64(timeStart / HLSSegmentTimeInSeconds);
                if (requestedSegmentSequence > 0)
                {
                    requestedSegmentSequence--; //1 segment file margin
                }
                string pathName    = FFMpegPlaylistManifest.GetPlaylistFolderFromTranscodeFile(_cachePath, transcodingFile);
                string playlist    = Path.Combine(pathName, PlaylistManifest.PLAYLIST_MANIFEST_FILE_NAME);
                string segmentFile = Path.Combine(pathName, requestedSegmentSequence.ToString("00000") + ".ts");
                if (File.Exists(playlist) == true && File.Exists(segmentFile) == true)
                {
                    //Use exisitng context if possible
                    TranscodeContext existingContext = await GetExistingTranscodeContextAsync(clientId, video.TranscodeId).ConfigureAwait(false);

                    if (existingContext != null)
                    {
                        if (existingContext.LastSegment > requestedSegmentSequence)
                        {
                            existingContext.TargetFile = playlist;
                            existingContext.SegmentDir = pathName;
                            if (existingContext.Stream == null)
                            {
                                existingContext.AssignStream(await GetFileStreamAsync(playlist).ConfigureAwait(false));
                            }
                            existingContext.HlsBaseUrl = video.HlsBaseUrl;
                            return(existingContext);
                        }
                    }
                    else
                    {
                        //Presume that it is a cached file
                        TouchDirectory(pathName);
                        context.Partial    = false;
                        context.TargetFile = playlist;
                        context.SegmentDir = pathName;
                        context.HlsBaseUrl = video.HlsBaseUrl;
                        context.AssignStream(await GetFileStreamAsync(playlist).ConfigureAwait(false));
                        return(context);
                    }
                }
            }

            FFMpegTranscodeData data = new FFMpegTranscodeData(_cachePath)
            {
                TranscodeId = video.TranscodeId, ClientId = clientId
            };

            if (string.IsNullOrEmpty(video.TranscoderBinPath) == false)
            {
                data.TranscoderBinPath = video.TranscoderBinPath;
            }
            if (string.IsNullOrEmpty(video.TranscoderArguments) == false)
            {
                data.ConcatedFileInput   = video.ConcatSourceMediaPaths;
                data.TranscoderArguments = video.TranscoderArguments;
                data.InputMediaFilePaths = video.SourceMediaPaths;
                if (video.PreferredSourceSubtitles != null)
                {
                    foreach (var mediaSourceIndex in video.PreferredSourceSubtitles.Keys)
                    {
                        foreach (var sub in video.PreferredSourceSubtitles[mediaSourceIndex])
                        {
                            if (string.IsNullOrEmpty(sub.SourcePath) == false)
                            {
                                data.AddSubtitle(mediaSourceIndex, sub.SourcePath);
                                context.TargetSubtitles.Add(sub.SourcePath);
                            }
                        }
                    }
                }
                data.OutputFilePath = transcodingFile;
                context.TargetFile  = transcodingFile;
            }
            else
            {
                data.Encoder = _ffMpegEncoderHandler.StartEncoding(video.TranscodeId, video.TargetVideoCodec);
                _ffMpegCommandline.InitTranscodingParameters(video.ConcatSourceMediaPaths, video.SourceMediaPaths, ref data);

                bool useX26XLib = video.TargetVideoCodec == VideoCodec.H264 || video.TargetVideoCodec == VideoCodec.H265;
                _ffMpegCommandline.AddTranscodingThreadsParameters(!useX26XLib, ref data);

                int subCopyStream = -1;
                if (video.PreferredSourceSubtitles.Any())
                {
                    if (video.FirstPreferredSourceSubtitle.IsEmbedded)
                    {
                        subCopyStream = video.FirstPreferredSourceSubtitle.StreamIndex;
                        _ffMpegCommandline.AddSubtitleCopyParameters(video.FirstPreferredSourceSubtitle, data);
                    }
                    else if (embeddedSupported)
                    {
                        foreach (int mediaSourceIndex in video.PreferredSourceSubtitles.Keys)
                        {
                            _ffMpegCommandline.AddSubtitleEmbeddingParameters(mediaSourceIndex, video.PreferredSourceSubtitles[mediaSourceIndex], embeddedSubCodec, timeStart, data);
                        }
                    }
                    else if (video.TargetSubtitleSupport != SubtitleSupport.SoftCoded)
                    {
                        video.TargetSubtitleSupport = SubtitleSupport.HardCoded; //Fallback to hardcoded subtitles
                        _logger.Debug("FFMpegMediaConverter: Soft subs not supported. Fallback to hardcoded subtitles");
                    }
                }
                else
                {
                    embeddedSupported = false;
                    data.OutputArguments.Add("-sn");
                }

                _ffMpegCommandline.AddTimeParameters(video, timeStart, timeDuration, data);
                _ffMpegCommandline.AddStreamMapParameters(video, data);

                FFMpegEncoderConfig encoderConfig = _ffMpegEncoderHandler.GetEncoderConfig(data.Encoder);
                _ffMpegCommandline.AddVideoParameters(video, data.TranscodeId, encoderConfig, data);
                _ffMpegCommandline.AddVideoAudioParameters(video, data);

                var result = await _ffMpegCommandline.AddTargetVideoFormatAndOutputFileParametersAsync(video, transcodingFile, timeStart, data).ConfigureAwait(false);

                context.TargetFile     = result.TranscodingFile;
                context.CurrentSegment = result.StartSegment;
                if (video.PreferredSourceSubtitles.Any())
                {
                    foreach (var sub in video.PreferredSourceSubtitles.SelectMany(s => s.Value))
                    {
                        if (string.IsNullOrEmpty(sub.SourcePath) == false)
                        {
                            context.TargetSubtitles.Add(sub.SourcePath);
                        }
                    }
                }
            }

            _logger.Info("FFMpegMediaConverter: Invoking transcoder to transcode video file '{0}' for transcode '{1}' with arguments '{2}'", video.SourceMediaPaths.First().Value, video.TranscodeId, String.Join(", ", data.OutputArguments.ToArray()));
            context.Start();
            context.AssignStream(await ExecuteTranscodingProcessAsync(data, context, waitForBuffer).ConfigureAwait(false));
            return(context);
        }