Beispiel #1
0
    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);
        }
    }
Beispiel #2
0
    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)));
        }
    }