/*=========================================================================

  Program:   ParaView
  Module:    vtkPVImageDelivery.cxx

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkPVImageDelivery.h"

#include "vtkDataEncoder.h"
#include "vtkImageData.h"
#include "vtkLogger.h"
#include "vtkObjectFactory.h"
#include "vtkOpenGLRenderWindow.h"
#include "vtkOpenGLVideoFrame.h"
#include "vtkPacket.h"
#include "vtkPixelFormatTypes.h"
#include "vtkPointData.h"
#include "vtkRawVideoFrame.h"
#include "vtkRenderWindow.h"
#include "vtkVector.h"
#include "vtkVideoEncoder.h"
#include "vtkWEBMMuxer.h"

#include <chrono>
#include <map>
#include <stdexcept>

//----------------------------------------------------------------------------
class vtkPVImageDelivery::vtkInternals
{
public:
  vtkTypeUInt32 GlobalID{ 0 };

  vtkWeakPointer<vtkVideoEncoder> VideoEncoder;
  vtkNew<vtkDataEncoder> JpegEncoder;
  vtkNew<vtkOpenGLVideoFrame> RawFrame;
  vtkNew<vtkWEBMMuxer> WebmMuxer;
  VTKVideoEncoderResultType VideoOutput;
  std::chrono::high_resolution_clock::duration CaptureTimeElapsed;
  std::chrono::high_resolution_clock::duration GPU2CPUXferTimeElapsed;
  std::chrono::high_resolution_clock::time_point MuxStartTimePoint{
    std::chrono::high_resolution_clock::now()
  };
  std::chrono::high_resolution_clock::time_point LastRenderTimePoint{
    std::chrono::high_resolution_clock::now()
  };

  bool LosslessMode{ false };
  VTKPixelFormatType LosslessPixFmt;
  vtkRawVideoFrame::SliceOrderType LosslessSliceOrder = vtkRawVideoFrame::SliceOrderType::BottomUp;
  bool JpegMode{ false };
  bool MediaSegmentsMode{ false };
  bool RequestMediaInitializationSegment{ false };
  long TargetVideoBitrate;
  int VideoQuantizer;
  std::map<EncoderQuality, int> StreamQuality;
  long FrameRate{ 0 };
  long DeliverCount{ 0 };
};

//----------------------------------------------------------------------------
vtkStandardNewMacro(vtkPVImageDelivery);

//----------------------------------------------------------------------------
vtkPVImageDelivery::vtkPVImageDelivery()
  : Internals(new vtkPVImageDelivery::vtkInternals())
{
}

//----------------------------------------------------------------------------
vtkPVImageDelivery::~vtkPVImageDelivery() = default;

//----------------------------------------------------------------------------
void vtkPVImageDelivery::PrintSelf(ostream& os, vtkIndent indent) {}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::SetGlobalID(vtkTypeUInt32 gid)
{
  vtkLogScopeF(TRACE, "%s gid=%d", __func__, gid);
  auto& internals = (*this->Internals);
  internals.GlobalID = gid;
}

//----------------------------------------------------------------------------
vtkTypeUInt32 vtkPVImageDelivery::GetGlobalID() const
{
  const auto& internals = (*this->Internals);
  return internals.GlobalID;
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::SetLosslessMode(bool value)
{
  vtkLogScopeF(TRACE, "%s value=%d", __func__, value);
  auto& internals = (*this->Internals);
  internals.LosslessMode = value;
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::SetLosslessPixelFormat(VTKPixelFormatType pixFmt)
{
  vtkLogScopeF(TRACE, "%s pixFmt=%s", __func__, vtkPixelFormatTypeUtilities::ToString(pixFmt));
  auto& internals = (*this->Internals);
  internals.LosslessPixFmt = pixFmt;
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::SetLosslessPictureSliceOrder(vtkRawVideoFrame::SliceOrderType sliceOrder)
{
  vtkLogScopeF(TRACE, "%s sliceOrder=%s", __func__, sliceOrder == 1 ? "bottom-up" : "top-down");
  auto& internals = (*this->Internals);
  internals.LosslessSliceOrder = sliceOrder;
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::SetLosslessPixelFormat(int pixFmt)
{
  if (pixFmt >= 0 && pixFmt <= 1)
  {
    this->SetLosslessPixelFormat(static_cast<VTKPixelFormatType>(pixFmt));
  }
  else
  {
    vtkLogIfF(ERROR, pixFmt >= 0 && pixFmt <= 3,
      "%s is unacceptable for lossless pixel format type.",
      vtkPixelFormatTypeUtilities::ToString(static_cast<VTKPixelFormatType>(pixFmt)));
    vtkLogIfF(ERROR, pixFmt < 0 || pixFmt > 3, "%d is unacceptable for lossless pixel format type.",
      pixFmt);
  }
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::SetLosslessPictureSliceOrder(int sliceOrder)
{
  if (sliceOrder == 0 || sliceOrder == 1)
  {
    this->SetLosslessPictureSliceOrder(static_cast<vtkRawVideoFrame::SliceOrderType>(sliceOrder));
  }
  else
  {
    vtkLogF(ERROR, "%d is unacceptable for lossless slice order type.", sliceOrder);
  }
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::SetJpegMode(bool value)
{
  vtkLogScopeF(TRACE, "%s value=%d", __func__, value);
  auto& internals = (*this->Internals);
  internals.JpegMode = value;
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::SetStillStreamQuality(int quality)
{
  vtkLogScopeF(TRACE, "%s quality=%d", __func__, quality);
  auto& internals = (*this->Internals);
  internals.StreamQuality[EncoderQuality::HIGH] = quality;
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::SetInteractiveStreamQuality(int quality)
{
  vtkLogScopeF(TRACE, "%s quality=%d", __func__, quality);
  auto& internals = (*this->Internals);
  internals.StreamQuality[EncoderQuality::LOW] = quality;
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::SetTargetBitrate(int bitrate)
{
  vtkLogScopeF(TRACE, "%s bitrate=%d", __func__, bitrate);
  auto& internals = (*this->Internals);
  internals.TargetVideoBitrate = bitrate;
  if (internals.VideoEncoder)
  {
    internals.VideoEncoder->SetBitRate(internals.TargetVideoBitrate * 1000);
  }
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::SetVideoEncoder(vtkVideoEncoder* encoder)
{
  vtkLogScopeF(TRACE, "%s encoder=%s", __func__, vtkLogIdentifier(encoder));
  auto& internals = (*this->Internals);
  // unset existing encoder
  if (internals.VideoEncoder != nullptr)
  {
    internals.VideoEncoder->Shutdown();
    internals.VideoEncoder = nullptr;
    // flush media segment muxer.
    if (internals.MediaSegmentsMode)
    {
      vtkLog(TRACE, "Write trailer");
      internals.WebmMuxer->WriteFileTrailer();
      internals.WebmMuxer->Flush();
    }
  }
  if (encoder == nullptr)
  {
    return;
  }
  internals.VideoEncoder = encoder;
  // configure for low-delay encoding purpose.
  internals.VideoEncoder->SetLowDelayMode(true);
  internals.VideoEncoder->SetBitRateControlMode(vtkVideoEncoder::BRCType::CBR);
  internals.VideoEncoder->SetBitRate(internals.TargetVideoBitrate * 1000);
  internals.VideoEncoder->SetGroupOfPicturesSize(300);
  internals.VideoEncoder->SetQuantizationParameter(internals.VideoQuantizer);
  internals.VideoEncoder->SetOutputHandler([this](VTKVideoEncoderResultType result) {
    vtkLogF(TRACE, "encoded-chunk-available");
    this->Internals->VideoOutput = result;
  });
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::SetMediaSegmentsMode(bool value)
{
  vtkLogScopeF(TRACE, "%s value=%d", __func__, value);
  auto& internals = (*this->Internals);
  internals.MediaSegmentsMode = value;
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::SetRequestMediaInitializationSegment(bool value)
{
  vtkLogScopeF(TRACE, "%s value=%d", __func__, value);
  auto& internals = (*this->Internals);
  internals.RequestMediaInitializationSegment = value;
}

//----------------------------------------------------------------------------
vtkPacket vtkPVImageDelivery::PackageDisplayFramebuffer(
  vtkRenderWindow* window, vtkRemoteViewStatsItem& stats, bool prefer_low_quality /*=false*/)
{
  vtkLogScopeF(TRACE, "%s prefer_low_quality=%d", __func__, prefer_low_quality);
  vtkSmartPointer<vtkUnsignedCharArray> content;
  auto result = this->SerializeDisplayFrameBuffer(window, stats, content, prefer_low_quality);

  if (!content)
  {
    return vtkPacket();
  }
  else
  {
    std::map<std::string, vtkSmartPointer<vtkObject>> payload;
    payload["content"] = content;
    auto json = result.ToJson();
    vtkLogF(TRACE, "%s", json.dump(2).c_str());
    return vtkPacket(json, payload);
  }
}

//----------------------------------------------------------------------------
VTKPixelFormatType vtkPVImageDelivery::GetOptimalStreamPixelFormat() const
{
  const auto& internals = (*this->Internals);
  if (internals.LosslessMode)
  {
    return internals.LosslessPixFmt;
  }
  else if (internals.JpegMode)
  {
    return VTKPixelFormatType::VTKPF_RGB24;
  }
  else if (this->GetUseVideoEncoders() && (internals.VideoEncoder != nullptr))
  {
    return internals.VideoEncoder->GetInputPixelFormat();
  }
  return VTKPixelFormatType::VTKPF_RGB24;
}

//----------------------------------------------------------------------------
vtkRawVideoFrame::SliceOrderType vtkPVImageDelivery::GetOptimalSliceOrder() const
{
  const auto& internals = (*this->Internals);
  if (internals.LosslessMode)
  {
    return internals.LosslessSliceOrder;
  }
  else if (internals.JpegMode)
  {
    return vtkRawVideoFrame::SliceOrderType::BottomUp;
  }
  else if (this->GetUseVideoEncoders())
  {
    return vtkRawVideoFrame::SliceOrderType::TopDown;
  }
  return vtkRawVideoFrame::SliceOrderType::BottomUp;
}

//----------------------------------------------------------------------------
const char* vtkPVImageDelivery::GetMimeType() const
{
  const auto& internals = (*this->Internals);
  vtkLogScopeF(TRACE, "%s, %d", __func__, internals.LosslessMode);
  if (this->GetUseVideoEncoders() && internals.VideoEncoder != nullptr)
  {
    switch (internals.VideoEncoder->GetCodec())
    {
      case VTKVideoCodecType::VTKVC_VP9:
        return internals.MediaSegmentsMode ? "video/webm; codecs=vp09.00.10.08"
                                           : "application/octet-stream";
      default:
        // we don't have muxers setup for the rest of the codecs.
        return "application/octet-stream";
    }
  }
  if (internals.JpegMode)
  {
    return "image/jpeg";
  }
  else if (internals.LosslessMode)
  {
    return "image/rgb24";
  }
  return "unknown";
}

//----------------------------------------------------------------------------
const char* vtkPVImageDelivery::GetCodecLongName() const
{
  const auto& internals = (*this->Internals);
  vtkLogScopeF(TRACE, "%s, %d", __func__, internals.LosslessMode);
  if (this->GetUseVideoEncoders() && internals.VideoEncoder != nullptr)
  {
    // TODO: Ideally, we should pull these out of the raw bitstream.
    switch (internals.VideoEncoder->GetCodec())
    {
      case VTKVideoCodecType::VTKVC_VP9:
        return "vp09.00.10.08";
      case VTKVideoCodecType::VTKVC_H264:
        // profile:"High", no constraints, level:6.2 (max 8k@120.9 fps)
        return "avc1.64003e";
      case VTKVideoCodecType::VTKVC_AV1:
        return "av01.0.01M.08";
      default:
        break;
    }
  }
  return "unknown";
}

//----------------------------------------------------------------------------
vtkRemoteViewResultItem vtkPVImageDelivery::SerializeDisplayFrameBuffer(vtkRenderWindow* window,
  vtkRemoteViewStatsItem& stats, vtkSmartPointer<vtkUnsignedCharArray>& content,
  bool prefer_low_quality /*=false*/)
{
  vtkLogScopeF(TRACE, "%s prefer_low_quality=%d", __func__, prefer_low_quality);
  auto& internals = (*this->Internals);

  this->Capture(window);

  vtkRemoteViewResultItem result;
  result.GlobalID = stats.GlobalID;
  result.Stats = stats;
  internals.DeliverCount += 1;

  result.Width = internals.RawFrame->GetWidth();
  result.Height = internals.RawFrame->GetHeight();

  // when images cannot/shouldn't be compressed, transmit rgba/rgb pixels.
  if (internals.LosslessMode ||
    (internals.VideoEncoder != nullptr &&
      !internals.VideoEncoder->SupportsCodec(internals.VideoEncoder->GetCodec())))
  {
    switch (internals.RawFrame->GetPixelFormat())
    {
      case VTKPixelFormatType::VTKPF_RGBA32:
        result.Mime = "image/rgba32";
        break;
      case VTKPixelFormatType::VTKPF_RGB24:
        result.Mime = "image/rgb24";
        break;
      default:
        result.Mime = "unknown";
        break;
    }
    result.Size = internals.RawFrame->GetActualSize();
    result.PTS = internals.DeliverCount;

    auto tStart = std::chrono::high_resolution_clock::now();
    content = internals.RawFrame->GetData();
    auto tStop = std::chrono::high_resolution_clock::now();

    result.Stats.GPU2CPUXferTime = (tStop - tStart).count();
    result.Stats.RenderPayloadSizeBytes = internals.RawFrame->GetActualSize();
  }
  else if (internals.JpegMode)
  {
    auto qualityMode = prefer_low_quality ? EncoderQuality::LOW : EncoderQuality::HIGH;
    this->DoImageEncode(result, content, qualityMode);
  }
  else
  {
    auto encGfxContext = internals.VideoEncoder->GetGraphicsContext();
    if (encGfxContext != window)
    {
      vtkLogF(TRACE, "Gfx context on VideoEncoder (%s) != given window (%s)",
        vtkLogIdentifier(internals.VideoEncoder->GetGraphicsContext()), vtkLogIdentifier(window));
      internals.VideoEncoder->SetGraphicsContext(window);
    }
    auto qualityMode = prefer_low_quality ? EncoderQuality::LOW : EncoderQuality::HIGH;
    this->DoVideoEncode(result, content, qualityMode);
    internals.VideoOutput.second.clear();
  }
  result.Stats.DisplayGrabTime = internals.CaptureTimeElapsed.count();
  result.Stats.Framerate = internals.FrameRate;
  return result;
}

//----------------------------------------------------------------------------
bool vtkPVImageDelivery::GetUseVideoEncoders() const
{
  const auto& internals = (*this->Internals);
  vtkLogScopeF(
    TRACE, "%s, lossless: %d jpeg: %d", __func__, internals.LosslessMode, internals.JpegMode);
  return !(internals.LosslessMode || internals.JpegMode);
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::Capture(vtkRenderWindow* window)
{
  vtkLogScopeFunction(TRACE);
  auto& internals = (*this->Internals);
  using SliceT = vtkRawVideoFrame::SliceOrderType;

  const auto framedelta =
    (std::chrono::high_resolution_clock::now() - internals.LastRenderTimePoint).count();
  internals.FrameRate = 1000000000 / framedelta;
  internals.LastRenderTimePoint = std::chrono::high_resolution_clock::now();
  // size of the display target
  vtkVector2i targetSize(window->GetSize());
  // recommended picture parameters
  const auto recPixFmt = this->GetOptimalStreamPixelFormat();
  const auto recSliceOrder = this->GetOptimalSliceOrder();
  const auto recWidth = window->GetSize()[0];
  const auto recHeight = window->GetSize()[1];
  // original picture parameters
  const auto origWidth = internals.RawFrame->GetWidth();
  const auto origHeight = internals.RawFrame->GetHeight();
  const auto origPixFmt = internals.RawFrame->GetPixelFormat();
  const auto origSliceOrder = internals.RawFrame->GetSliceOrderType();

  if (recWidth != targetSize[0] || recHeight != targetSize[1])
  {
    vtkLog(ERROR, "Render window not yet setup.");
  }

  // Developer note: uncomment to inspect the contents of server side DisplayFrameBuffer.
  // on macos, it will not work.
  // window->OffScreenRenderingOff();

  if (auto glWindow = vtkOpenGLRenderWindow::SafeDownCast(window))
  {
    // if any of original picture parameters != recommended, then re-init frame.
    if (recWidth != origWidth || recHeight != origHeight || recPixFmt != origPixFmt ||
      recSliceOrder != origSliceOrder)
    {
      // initialize graphics resources for our frame.
      internals.RawFrame->ReleaseGraphicsResources();
      internals.RawFrame->SetContext(glWindow);
      // apply recommendations.
      internals.RawFrame->SetWidth(recWidth);
      internals.RawFrame->SetHeight(recHeight);
      internals.RawFrame->SetPixelFormat(recPixFmt);
      internals.RawFrame->SetSliceOrderType(recSliceOrder);
      internals.RawFrame->AllocateDataStore();
    }
  }
  else
  {
    vtkLogF(
      ERROR, "vtkPVImageDelivery::Capture only supports vtkOpenGLRenderWindow and subclasses");
    throw std::runtime_error("unexpected error occurred");
  }

  auto tStart = std::chrono::high_resolution_clock::now();
  internals.RawFrame->Capture(window);
  auto tStop = std::chrono::high_resolution_clock::now();
  internals.CaptureTimeElapsed = tStop - tStart;
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::DoImageEncode(vtkRemoteViewResultItem& result,
  vtkSmartPointer<vtkUnsignedCharArray>& content, EncoderQuality qualityMode)
{
  auto& internals = (*this->Internals);
  // copy frame into image data
  auto data = vtk::TakeSmartPointer(vtkImageData::New());
  data->SetDimensions(internals.RawFrame->GetWidth(), internals.RawFrame->GetHeight(), 1);
  auto tStart = std::chrono::high_resolution_clock::now();
  data->GetPointData()->SetScalars(internals.RawFrame->GetData());
  auto tStop = std::chrono::high_resolution_clock::now();
  internals.GPU2CPUXferTimeElapsed = tStop - tStart;

  // pass to encoder
  int quality = internals.StreamQuality[qualityMode];
  internals.JpegEncoder->Push(result.GlobalID, data, quality, /* encoding */ 0);

  // block for latest image only if we need a high quality rendering otherwise
  // we get whatever is available. This may create a delay in a form of a
  // "sliding window", i.e. all results are shifted by a delay of the
  // initial render.  The delay should be small for lower resolutions. For
  // higher resolutions, video encoders are better anyway.
  if (qualityMode == EncoderQuality::HIGH)
  {
    internals.JpegEncoder->Flush(result.GlobalID);
  }
  internals.JpegEncoder->GetLatestOutput(result.GlobalID, content);

  if (content)
  {

    result.Mime = this->GetMimeType();
    result.Size = content->GetDataSize();
    result.PTS = internals.DeliverCount;

    result.Stats.EncodeTime = (std::chrono::high_resolution_clock::now() - tStop).count();
    result.Stats.RenderPayloadSizeBytes = content->GetDataSize();
    result.Stats.GPU2CPUXferTime = internals.GPU2CPUXferTimeElapsed.count();
  }
}

//----------------------------------------------------------------------------
void vtkPVImageDelivery::DoVideoEncode(vtkRemoteViewResultItem& result,
  vtkSmartPointer<vtkUnsignedCharArray>& content, EncoderQuality qualityMode)
{
  auto& internals = (*this->Internals);
  if (internals.VideoEncoder == nullptr)
  {
    vtkLogF(ERROR, "Unable to get a video encoder.");
    throw std::runtime_error("unexpected error occurred");
  }
  // check if a new quantizer needs to be set.
  const auto oldQuantizer = internals.VideoEncoder->GetQuantizationParameter();
  // quantizer must lie within 1-60, where 1: best quality, 60: worst quality
  internals.VideoQuantizer = 61 - int(internals.StreamQuality[qualityMode] / 101.0f * 60) + 1;
  internals.VideoEncoder->SetQuantizationParameter(internals.VideoQuantizer);
  // request key frames for higher quality pictures.
  internals.VideoEncoder->SetForceIFrame(this->NeedKeyFrame(qualityMode));
  // populates VideoOutput
  internals.VideoEncoder->Encode(internals.RawFrame);

  if (internals.VideoOutput.second.empty() || internals.VideoOutput.second.front() == nullptr)
  {
    content = vtk::TakeSmartPointer(vtkUnsignedCharArray::New());
    result.Size = 0;
    return;
  }
  vtkSmartPointer<vtkCompressedVideoPacket> bitstreamChunk;
  if (internals.MediaSegmentsMode &&
    internals.VideoEncoder->GetCodec() == VTKVideoCodecType::VTKVC_VP9)
  {
    bitstreamChunk = this->GenerateMediaSegments(internals.VideoOutput.second.front());
  }
  else
  {
    bitstreamChunk = internals.VideoOutput.second.front();
  }
  result.Mime = this->GetMimeType();
  result.Size = bitstreamChunk->GetSize();
  result.Width = bitstreamChunk->GetDisplayWidth();
  result.Height = bitstreamChunk->GetDisplayHeight();
  result.Codec = internals.VideoEncoder->GetCodec();
  result.CodecLongName = this->GetCodecLongName();
  result.KeyFrame = bitstreamChunk->GetIsKeyFrame();
  result.PTS = bitstreamChunk->GetPresentationTS();

  content = bitstreamChunk->GetData();

  result.Stats.EncodeTime = internals.VideoEncoder->GetLastEncodeTimeNS();
  result.Stats.GPU2CPUXferTime = internals.VideoEncoder->GetLastScaleTimeNS();
  result.Stats.RenderPayloadSizeBytes = bitstreamChunk->GetSize();
}

//----------------------------------------------------------------------------
vtkSmartPointer<vtkCompressedVideoPacket> vtkPVImageDelivery::GenerateMediaSegments(
  vtkSmartPointer<vtkCompressedVideoPacket> encodedVideoChunk)
{
  auto& internals = (*this->Internals);
  if (internals.RequestMediaInitializationSegment)
  {
    vtkLog(TRACE, "Write trailer");
    internals.WebmMuxer->WriteFileTrailer();
    // don't send that trailer to listener, it wouldn't understand.
    internals.WebmMuxer->Flush();
    // reset the request so that client doesn't have to maintain a record of it.
    internals.RequestMediaInitializationSegment = false;
  }
  if (!internals.WebmMuxer->GetIsHeaderWritten())
  {
    vtkLog(TRACE, "Write header");
    internals.WebmMuxer->SetWidth(encodedVideoChunk->GetDisplayWidth());
    internals.WebmMuxer->SetHeight(encodedVideoChunk->GetDisplayHeight());
    internals.WebmMuxer->SetTimeStampScale(1000000);
    internals.WebmMuxer->SetWriteToMemory(true);
    internals.WebmMuxer->WriteVP9FileHeader();
    internals.WebmMuxer->SetForceNewClusters(false);
    internals.WebmMuxer->SetLiveMode(true);
    internals.WebmMuxer->SetOutputCues(false);
    internals.MuxStartTimePoint = std::chrono::high_resolution_clock::now();
  }
  auto pts = encodedVideoChunk->GetPresentationTS();
  auto pts_live = (std::chrono::high_resolution_clock::now() - internals.MuxStartTimePoint).count();
  encodedVideoChunk->SetPresentationTS(pts_live);
  internals.WebmMuxer->WriteWebmBlock(encodedVideoChunk);

  auto segmentChunk = vtk::TakeSmartPointer(vtkCompressedVideoPacket::New());
  segmentChunk->CopyMetadata(encodedVideoChunk);
  segmentChunk->CopyData(internals.WebmMuxer->GetDynamicBuffer());
  vtkLogF(TRACE, "Write block %d", segmentChunk->GetSize());
  internals.WebmMuxer->Flush();
  segmentChunk->SetPresentationTS(pts);
  return segmentChunk;
}

bool vtkPVImageDelivery::NeedKeyFrame(EncoderQuality qualityMode)
{
  auto& internals = (*this->Internals);
  // a chunk with initialization segment must be key frame.
  // high quality pictures must be key frames.
  return internals.RequestMediaInitializationSegment || (qualityMode == EncoderQuality::HIGH);
}
