private async Task <Stream> ExecuteTranscodingProcessAsync(FFMpegTranscodeData data, FFMpegTranscodeContext context, bool waitForBuffer) { if (context.Partial == true || await IsTranscodeRunningAsync(data.ClientId, data.TranscodeId).ConfigureAwait(false) == false) { try { context.Start(); await AddTranscodeContextAsync(data.ClientId, data.TranscodeId, context).ConfigureAwait(false); //context.TargetFile = Path.Combine(data.WorkPath, data.SegmentPlaylist != null ? data.SegmentPlaylist : data.OutputFilePath); //if(context.TargetFile.EndsWith("pipe:")) //{ // context.TargetFile = ""; //} context.Live = data.IsLive; context.SegmentDir = null; if (data.SegmentPlaylist != null) { context.SegmentDir = data.WorkPath; } string name = "MP Transcode - " + data.TranscodeId; if (context.Partial) { name += " - Partial: " + Thread.CurrentThread.ManagedThreadId; } Thread transcodeThread = new Thread(TranscodeProcessor) { //IsBackground = true, //Can cause invalid cache files Name = name, Priority = ThreadPriority.Normal }; FFMpegTranscodeThreadData threadData = new FFMpegTranscodeThreadData() { TranscodeData = data, Context = context }; transcodeThread.Start(threadData); } catch { _ffMpegEncoderHandler.EndEncoding(data.Encoder, data.TranscodeId); context.Fail(); await RemoveTranscodeContextAsync(data.ClientId, data.TranscodeId, context).ConfigureAwait(false); throw; } } if (waitForBuffer == false) { return(null); } return(await GetTranscodedFileBufferAsync(data, context).ConfigureAwait(false)); }
internal static async Task CreatePlaylistFilesAsync(FFMpegTranscodeData data) { if (Directory.Exists(data.WorkPath) == false) { Directory.CreateDirectory(data.WorkPath); } try { if (data.SegmentPlaylistData != null) { string playlist = Path.Combine(data.WorkPath, BaseMediaConverter.PLAYLIST_FILE_NAME); string tempPlaylist = playlist + ".tmp"; using (FileStream fileStream = File.Open(tempPlaylist, FileMode.Create, FileAccess.Write, FileShare.None)) await data.SegmentPlaylistData.CopyToAsync(fileStream); MoveFile(tempPlaylist, playlist); if (data.SegmentSubsPlaylistData != null) { playlist = Path.Combine(data.WorkPath, BaseMediaConverter.PLAYLIST_SUBTITLE_FILE_NAME); tempPlaylist = playlist + ".tmp"; using (FileStream fileStream = File.Open(tempPlaylist, FileMode.Create, FileAccess.Write, FileShare.None)) await data.SegmentSubsPlaylistData.CopyToAsync(fileStream); MoveFile(tempPlaylist, playlist); } } if (data.SegmentPlaylist != null && data.SegmentManifestData != null) { string tempManifest = data.SegmentPlaylist + ".tmp"; using (FileStream fileStream = File.Open(tempManifest, FileMode.Create, FileAccess.Write, FileShare.None)) await data.SegmentManifestData.CopyToAsync(fileStream); MoveFile(tempManifest, data.SegmentPlaylist); } } finally { //No need to keep data so free used memory data.SegmentManifestData?.Dispose(); data.SegmentPlaylistData?.Dispose(); data.SegmentSubsPlaylistData?.Dispose(); } }
private async Task <Stream> GetTranscodedFileBufferAsync(FFMpegTranscodeData data, FFMpegTranscodeContext context) { try { if (data.IsLive == true && data.SegmentPlaylist == null) { int iTry = 60; while (iTry > 0 && context.Failed == false && context.Aborted == false) { bool streamReady = false; try { if (data.LiveStream != null) { streamReady = data.LiveStream.CanRead; } } catch { } if (streamReady) { _logger.Debug(string.Format("FFMpegMediaConverter: Serving transcoded stream '{0}'", data.TranscodeId)); return(new BufferedStream(data.LiveStream)); } iTry--; await Task.Delay(500).ConfigureAwait(false); } _logger.Error("FFMpegMediaConverter: Timed out waiting for transcoded stream '{0}'", data.TranscodeId); } else { string fileReturnPath = ""; if (data.SegmentPlaylist != null) { fileReturnPath = Path.Combine(data.WorkPath, data.SegmentPlaylist); } else { fileReturnPath = Path.Combine(data.WorkPath, data.OutputFilePath); } int iTry = 60; while (iTry > 0 && context.Failed == false && context.Aborted == false) { if (File.Exists(fileReturnPath)) { long length = 0; try { length = new FileInfo(fileReturnPath).Length; } catch { } if (length > 0) { _logger.Debug(string.Format("FFMpegMediaConverter: Serving transcoded file '{0}'", fileReturnPath)); if (data.SegmentPlaylist != null) { return(await PlaylistManifest.CorrectPlaylistUrlsAsync(data.SegmentBaseUrl, fileReturnPath).ConfigureAwait(false)); } else { Stream stream = new FileStream(fileReturnPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); return(stream); } } } iTry--; await Task.Delay(500).ConfigureAwait(false); } _logger.Error("FFMpegMediaConverter: Timed out waiting for transcoded file '{0}'", fileReturnPath); } } catch (Exception ex) { _logger.Error("FFMpegMediaConverter: Error waiting for transcoding '{0}'", ex, data.TranscodeId); } return(null); }
protected override async Task <TranscodeContext> TranscodeImageAsync(string clientId, ImageTranscoding image, bool waitForBuffer) { FFMpegTranscodeContext context = new FFMpegTranscodeContext(_cacheEnabled, _cachePath); context.Partial = false; string transcodingFile = Path.Combine(_cachePath, GetTranscodingImageFileName(image)); if (File.Exists(transcodingFile) == true) { //Use existing context if possible TranscodeContext existingContext = await GetExistingTranscodeContextAsync(clientId, image.TranscodeId).ConfigureAwait(false); if (existingContext != null) { existingContext.TargetFile = transcodingFile; if (existingContext.Stream == null) { existingContext.AssignStream(await GetFileStreamAsync(transcodingFile).ConfigureAwait(false)); } 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); } } FFMpegTranscodeData data = new FFMpegTranscodeData(_cachePath) { TranscodeId = image.TranscodeId, ClientId = clientId }; if (string.IsNullOrEmpty(image.TranscoderBinPath) == false) { data.TranscoderBinPath = image.TranscoderBinPath; } if (string.IsNullOrEmpty(image.TranscoderArguments) == false) { data.ConcatedFileInput = image.ConcatSourceMediaPaths; data.TranscoderArguments = image.TranscoderArguments; data.InputMediaFilePaths = image.SourceMediaPaths; } else { _ffMpegCommandline.InitTranscodingParameters(image.ConcatSourceMediaPaths, image.SourceMediaPaths, ref data); _ffMpegCommandline.AddTranscodingThreadsParameters(true, ref data); _ffMpegCommandline.AddImageParameters(image, data); data.InputArguments[data.FirstResourceIndex].Add("-f image2pipe"); //pipe works with network drives data.OutputArguments.Add("-f image2"); } data.OutputFilePath = transcodingFile; context.TargetFile = transcodingFile; _logger.Debug("FFMpegMediaConverter: Invoking transcoder to transcode image file '{0}' for transcode '{1}'", image.SourceMediaPaths, image.TranscodeId); context.Start(); context.AssignStream(await ExecuteTranscodingProcessAsync(data, context, waitForBuffer).ConfigureAwait(false)); return(context); }
protected override async Task <TranscodeContext> TranscodeAudioAsync(string clientId, AudioTranscoding audio, double timeStart, double timeDuration, bool waitForBuffer) { FFMpegTranscodeContext context = new FFMpegTranscodeContext(_cacheEnabled, _cachePath); context.TargetDuration = audio.SourceDuration.Value; if (timeStart == 0 && audio.TargetIsLive == false && _cacheEnabled) { timeDuration = 0; context.Partial = false; } else { audio.TargetIsLive = true; context.Partial = true; } if (audio.TargetAudioContainer == AudioContainer.Unknown) { audio.TargetAudioContainer = audio.SourceAudioContainer; } string transcodingFile = GetTranscodingAudioFileName(audio, timeStart); transcodingFile = Path.Combine(_cachePath, transcodingFile); if (File.Exists(transcodingFile)) { //Use non-partial context if possible TranscodeContext existingContext = await GetExistingTranscodeContextAsync(clientId, audio.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 (audio.TargetAudioBitrate.HasValue) { bitrate = audio.TargetAudioBitrate.Value; } else if (audio.SourceAudioBitrate.HasValue) { bitrate = audio.SourceAudioBitrate.Value; } 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); } } FFMpegTranscodeData data = new FFMpegTranscodeData(_cachePath) { TranscodeId = audio.TranscodeId, ClientId = clientId }; if (string.IsNullOrEmpty(audio.TranscoderBinPath) == false) { data.TranscoderBinPath = audio.TranscoderBinPath; } if (string.IsNullOrEmpty(audio.TranscoderArguments) == false) { data.ConcatedFileInput = audio.ConcatSourceMediaPaths; data.TranscoderArguments = audio.TranscoderArguments; data.InputMediaFilePaths = audio.SourceMediaPaths; data.OutputFilePath = transcodingFile; context.TargetFile = transcodingFile; } else { _ffMpegCommandline.InitTranscodingParameters(audio.ConcatSourceMediaPaths, audio.SourceMediaPaths, ref data); _ffMpegCommandline.AddTranscodingThreadsParameters(true, ref data); _ffMpegCommandline.AddTimeParameters(audio, timeStart, timeDuration, data); _ffMpegCommandline.AddAudioParameters(audio, data); context.TargetFile = await _ffMpegCommandline.AddTargetAudioFormatAndOutputFileParametersAsync(audio, transcodingFile, data).ConfigureAwait(false); data.OutputArguments.Add("-vn"); } _logger.Debug("FFMpegMediaConverter: Invoking transcoder to transcode audio file '{0}' for transcode '{1}'", audio.SourceMediaPaths, audio.TranscodeId); context.Start(); context.AssignStream(await ExecuteTranscodingProcessAsync(data, context, waitForBuffer).ConfigureAwait(false)); return(context); }
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); }
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); }