private async Task SaveTroubleshootingData(string channelNumber, string output) { try { var directory = new DirectoryInfo(Path.Combine(FileSystemLayout.TranscodeFolder, channelNumber)); FileInfo[] allFiles = directory.GetFiles(); string playlistFileName = Path.Combine(FileSystemLayout.TranscodeFolder, channelNumber, "live.m3u8"); string playlistContents = string.Empty; if (_localFileSystem.FileExists(playlistFileName)) { playlistContents = await File.ReadAllTextAsync(playlistFileName); } var data = new TroubleshootingData(allFiles, playlistContents, output); string serialized = data.Serialize(); string file = _tempFilePool.GetNextTempFile(TempFileCategory.BadTranscodeFolder); await File.WriteAllTextAsync(file, serialized); _logger.LogWarning("Transcode folder is in bad state; troubleshooting info saved to {File}", file); } catch (Exception ex) { _client.Notify(ex); } }
public TrimPlaylistResult TrimPlaylist( DateTimeOffset playlistStart, DateTimeOffset filterBefore, string[] lines, int maxSegments = 10, bool endWithDiscontinuity = false) { try { DateTimeOffset currentTime = playlistStart; DateTimeOffset nextPlaylistStart = DateTimeOffset.MaxValue; var discontinuitySequence = 0; var startSequence = 0; var output = new StringBuilder(); var started = false; var i = 0; var segments = 0; while (!lines[i].StartsWith("#EXTINF:")) { if (lines[i].StartsWith("#EXT-X-DISCONTINUITY-SEQUENCE")) { discontinuitySequence = int.Parse(lines[i].Split(':')[1]); } i++; } while (i < lines.Length) { if (segments >= maxSegments) { break; } string line = lines[i]; // _logger.LogInformation("Line: {Line}", line); if (line.StartsWith("#EXT-X-DISCONTINUITY")) { if (started) { output.AppendLine("#EXT-X-DISCONTINUITY"); } else { discontinuitySequence++; } i++; continue; } var duration = TimeSpan.FromSeconds( double.Parse( lines[i].TrimEnd(',').Split(':')[1], NumberStyles.Number, CultureInfo.InvariantCulture)); if (currentTime < filterBefore) { currentTime += duration; i += 3; continue; } nextPlaylistStart = currentTime < nextPlaylistStart ? currentTime : nextPlaylistStart; if (!started) { startSequence = int.Parse(lines[i + 2].Replace("live", string.Empty).Split('.')[0]); output.AppendLine("#EXTM3U"); output.AppendLine("#EXT-X-VERSION:6"); output.AppendLine("#EXT-X-TARGETDURATION:4"); output.AppendLine($"#EXT-X-MEDIA-SEQUENCE:{startSequence}"); output.AppendLine($"#EXT-X-DISCONTINUITY-SEQUENCE:{discontinuitySequence}"); output.AppendLine("#EXT-X-INDEPENDENT-SEGMENTS"); output.AppendLine("#EXT-X-DISCONTINUITY"); started = true; } output.AppendLine(lines[i]); string offset = currentTime.ToString("zzz").Replace(":", string.Empty); output.AppendLine($"#EXT-X-PROGRAM-DATE-TIME:{currentTime:yyyy-MM-ddTHH:mm:ss.fff}{offset}"); output.AppendLine(lines[i + 2]); currentTime += duration; segments++; i += 3; } var playlist = output.ToString(); if (endWithDiscontinuity && !playlist.EndsWith($"#EXT-X-DISCONTINUITY{Environment.NewLine}")) { playlist += "#EXT-X-DISCONTINUITY" + Environment.NewLine; } if (playlist.Trim().Split(Environment.NewLine).All(l => l.StartsWith('#'))) { throw new Exception("Trimming playlist to nothing"); } return(new TrimPlaylistResult(nextPlaylistStart, startSequence, playlist, segments)); } catch (Exception ex) { try { string file = _tempFilePool.GetNextTempFile(TempFileCategory.BadPlaylist); File.WriteAllLines(file, lines); _logger.LogError(ex, "Error filtering playlist. Bad playlist saved to {BadPlaylistFile}", file); // TODO: better error result? return(new TrimPlaylistResult(playlistStart, 0, string.Empty, 0)); } catch { // do nothing } throw; } }
public async Task <Either <BaseError, string> > GenerateSongImage( string ffmpegPath, string ffprobePath, Option <string> subtitleFile, Channel channel, Option <ChannelWatermark> playoutItemWatermark, Option <ChannelWatermark> globalWatermark, MediaVersion videoVersion, string videoPath, bool boxBlur, Option <string> watermarkPath, WatermarkLocation watermarkLocation, int horizontalMarginPercent, int verticalMarginPercent, int watermarkWidthPercent, CancellationToken cancellationToken) { try { string outputFile = _tempFilePool.GetNextTempFile(TempFileCategory.SongBackground); MediaStream videoStream = await _ffmpegStreamSelector.SelectVideoStream(videoVersion); Option <ChannelWatermark> watermarkOverride = videoVersion is FallbackMediaVersion or CoverArtMediaVersion ? new ChannelWatermark { Mode = ChannelWatermarkMode.Permanent, HorizontalMarginPercent = horizontalMarginPercent, VerticalMarginPercent = verticalMarginPercent, Location = watermarkLocation, Size = WatermarkSize.Scaled, WidthPercent = watermarkWidthPercent, Opacity = 100 } : None; Option <WatermarkOptions> watermarkOptions = await GetWatermarkOptions( ffprobePath, channel, playoutItemWatermark, globalWatermark, videoVersion, watermarkOverride, watermarkPath); FFmpegPlaybackSettings playbackSettings = _playbackSettingsCalculator.CalculateErrorSettings(channel.FFmpegProfile); FFmpegPlaybackSettings scalePlaybackSettings = _playbackSettingsCalculator.CalculateSettings( StreamingMode.TransportStream, channel.FFmpegProfile, videoVersion, videoStream, None, DateTimeOffset.UnixEpoch, DateTimeOffset.UnixEpoch, TimeSpan.Zero, TimeSpan.Zero, false, Option <int> .None); FFmpegProcessBuilder builder = new FFmpegProcessBuilder(ffmpegPath, false, _logger) .WithThreads(1) .WithQuiet() .WithFormatFlags(playbackSettings.FormatFlags) .WithSongInput(videoPath, videoStream.Codec, videoStream.PixelFormat, boxBlur) .WithWatermark(watermarkOptions, None, channel.FFmpegProfile.Resolution) .WithSubtitleFile(subtitleFile); foreach (IDisplaySize scaledSize in scalePlaybackSettings.ScaledSize) { builder = builder.WithScaling(scaledSize); if (NeedToPad(channel.FFmpegProfile.Resolution, scaledSize)) { builder = builder.WithBlackBars(channel.FFmpegProfile.Resolution); } } using Process process = builder .WithFilterComplex( videoStream, None, videoPath, None, playbackSettings.VideoFormat) .WithOutputFormat("apng", outputFile) .Build(); _logger.LogInformation( "ffmpeg song arguments {FFmpegArguments}", string.Join(" ", process.StartInfo.ArgumentList)); await Cli.Wrap(process.StartInfo.FileName) .WithArguments(process.StartInfo.ArgumentList) .WithValidation(CommandResultValidation.None) .ExecuteAsync(cancellationToken); return(outputFile); } catch (Exception ex) { _logger.LogWarning(ex, "Error generating song image"); _client.Notify(ex); return(Left(BaseError.New(ex.Message))); } }