diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index c98cb5c2a..666cec629 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -93,7 +93,7 @@ bool Decoder::Open(const CodecStream &stream) } } -TexturePtr Decoder::RetrieveVideo(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ÷r, CancelAtom *cancelled) +TexturePtr Decoder::RetrieveVideo(const RetrieveVideoParams &p) { QMutexLocker locker(&mutex_); @@ -109,16 +109,16 @@ TexturePtr Decoder::RetrieveVideo(Renderer *renderer, const rational &timecode, return nullptr; } - if (cancelled && cancelled->IsCancelled()) { + if (p.cancelled && p.cancelled->IsCancelled()) { return nullptr; } - if (cached_texture_ && cached_time_ == timecode) { + if (cached_texture_ && cached_time_ == p.time) { return cached_texture_; } - cached_texture_ = RetrieveVideoInternal(renderer, timecode, divider, cancelled); - cached_time_ = timecode; + cached_texture_ = RetrieveVideoInternal(p); + cached_time_ = p.time; return cached_texture_; } @@ -280,11 +280,9 @@ int64_t Decoder::GetImageSequenceIndex(const QString &filename) return number_only.toLongLong(); } -TexturePtr Decoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ÷r, CancelAtom *cancelled) +TexturePtr Decoder::RetrieveVideoInternal(const RetrieveVideoParams &p) { - Q_UNUSED(timecode) - Q_UNUSED(divider) - Q_UNUSED(cancelled) + Q_UNUSED(p) return nullptr; } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 665f21cc8..841d1901c 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -164,29 +164,12 @@ public: struct RetrieveVideoParams { - RetrieveVideoParams() - { - divider = 1; - maximum_format = VideoParams::kFormatInvalid; - } - - int divider; - VideoParams::Format maximum_format; - - void reset() - { - *this = RetrieveVideoParams(); - } - - bool operator==(const RetrieveVideoParams& rhs) const - { - return divider == rhs.divider && maximum_format == rhs.maximum_format; - } - - bool operator!=(const RetrieveVideoParams& rhs) const - { - return !(*this == rhs); - } + Renderer *renderer = nullptr; + rational time; + int divider = 1; + VideoParams::Format maximum_format = VideoParams::kFormatInvalid; + CancelAtom *cancelled = nullptr; + VideoParams::ColorRange force_range = VideoParams::kColorRangeDefault; }; /** @@ -199,7 +182,7 @@ public: * * This function is thread safe and can only run while the decoder is open. \see Open() */ - TexturePtr RetrieveVideo(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, CancelAtom *cancelled = nullptr); + TexturePtr RetrieveVideo(const RetrieveVideoParams& p); enum RetrieveAudioStatus { kInvalid = -1, @@ -294,7 +277,7 @@ protected: * Sub-classes must override this function IF they support video. Function is already mutexed * so sub-classes don't need to worry about thread safety. */ - virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, CancelAtom *cancelled); + virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p); virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, CancelAtom *cancelled); diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 31b187a5a..8e6642449 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -90,7 +90,6 @@ EncodingParams::EncodingParams() : video_buffer_size_(0), video_threads_(0), video_is_image_sequence_(false), - video_color_range_(kYUVDefault), audio_enabled_(false), audio_bit_rate_(0), subtitles_enabled_(false), diff --git a/app/codec/encoder.h b/app/codec/encoder.h index c77c6e1f2..2982764ed 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -43,14 +43,6 @@ using EncoderPtr = std::shared_ptr; class EncodingParams { public: - enum YUVRange - { - kYUVMPEG16_235, - kYUVJPEG0_255, - - kYUVDefault = kYUVMPEG16_235 - }; - EncodingParams(); void SetFilename(const QString& filename) { filename_ = filename; } @@ -75,7 +67,6 @@ public: void set_video_threads(const int& threads) { video_threads_ = threads; } void set_video_pix_fmt(const QString& s) { video_pix_fmt_ = s; } void set_video_is_image_sequence(bool s) { video_is_image_sequence_ = s; } - void set_video_color_range(YUVRange r) { video_color_range_ = r; } void set_color_transform(const ColorTransform& color_transform) { color_transform_ = color_transform; } const QString& filename() const { return filename_; } @@ -91,7 +82,6 @@ public: const int& video_threads() const { return video_threads_; } const QString& video_pix_fmt() const { return video_pix_fmt_; } bool video_is_image_sequence() const { return video_is_image_sequence_; } - YUVRange video_color_range() const { return video_color_range_; } const ColorTransform& color_transform() const { return color_transform_; } bool audio_enabled() const { return audio_enabled_; } @@ -126,7 +116,6 @@ private: int video_threads_; QString video_pix_fmt_; bool video_is_image_sequence_; - YUVRange video_color_range_; ColorTransform color_transform_; bool audio_enabled_; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 036e8e4d7..e6c5fa4ba 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -141,28 +141,32 @@ bool FFmpegDecoder::OpenInternal() return output_frame; }*/ -TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ¶ms, CancelAtom *cancelled) +TexturePtr FFmpegDecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) { - if (AVFramePtr f = RetrieveFrame(timecode, cancelled)) { - if (cancelled && cancelled->IsCancelled()) { + if (AVFramePtr f = RetrieveFrame(p.time, p.cancelled)) { + if (p.cancelled && p.cancelled->IsCancelled()) { return nullptr; } - if (InitScaler(f.get(), params)) { + int &src_fmt = f.get()->format; + src_fmt = FFmpegUtils::ConvertJPEGSpaceToRegularSpace(static_cast(src_fmt)); + + f->color_range = p.force_range == VideoParams::kColorRangeFull ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; + + if (InitScaler(f.get(), p)) { VideoParams vp(instance_.avstream()->codecpar->width, instance_.avstream()->codecpar->height, native_output_pix_fmt_, native_channel_count_, av_guess_sample_aspect_ratio(instance_.fmt_ctx(), instance_.avstream(), nullptr), VideoParams::kInterlaceNone, - params.divider); + p.divider); TexturePtr tex = nullptr; - const bool hwscale = true; + const bool hwscale = false; // Attempt to use GLSL shader for faster YUV to RGB conversion if (hwscale) { - AVPixelFormat src_fmt = AVPixelFormat(f.get()->format); if (src_fmt == AV_PIX_FMT_YUV420P || src_fmt == AV_PIX_FMT_YUV422P || src_fmt == AV_PIX_FMT_YUV444P @@ -171,13 +175,10 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration || src_fmt == AV_PIX_FMT_YUV444P10LE || src_fmt == AV_PIX_FMT_YUV420P12LE || src_fmt == AV_PIX_FMT_YUV422P12LE - || src_fmt == AV_PIX_FMT_YUV444P12LE - || src_fmt == AV_PIX_FMT_YUVJ420P - || src_fmt == AV_PIX_FMT_YUVJ422P - || src_fmt == AV_PIX_FMT_YUVJ444P) { + || src_fmt == AV_PIX_FMT_YUV444P12LE) { if (Yuv2RgbShader.isNull()) { // Compile shader - Yuv2RgbShader = renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag")))); + Yuv2RgbShader = p.renderer->CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/yuv2rgb.frag")))); } if (!Yuv2RgbShader.isNull()) { @@ -187,9 +188,6 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUV422P: case AV_PIX_FMT_YUV444P: - case AV_PIX_FMT_YUVJ420P: - case AV_PIX_FMT_YUVJ422P: - case AV_PIX_FMT_YUVJ444P: default: px_size = 1; bits_per_pixel = 8; @@ -208,20 +206,14 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration break; } - bool full_range = src_fmt == AV_PIX_FMT_YUVJ420P - || src_fmt == AV_PIX_FMT_YUVJ422P - || src_fmt == AV_PIX_FMT_YUVJ444P; - VideoParams plane_params = vp; plane_params.set_channel_count(1); plane_params.set_divider(1); plane_params.set_format(native_internal_pix_fmt_); - TexturePtr y_plane = renderer->CreateTexture(plane_params, f->data[0], f->linesize[0] / px_size); + TexturePtr y_plane = p.renderer->CreateTexture(plane_params, f->data[0], f->linesize[0] / px_size); if (src_fmt == AV_PIX_FMT_YUV420P || src_fmt == AV_PIX_FMT_YUV422P - || src_fmt == AV_PIX_FMT_YUVJ420P - || src_fmt == AV_PIX_FMT_YUVJ422P || src_fmt == AV_PIX_FMT_YUV420P10LE || src_fmt == AV_PIX_FMT_YUV422P10LE || src_fmt == AV_PIX_FMT_YUV420P12LE @@ -230,21 +222,20 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration } if (src_fmt == AV_PIX_FMT_YUV420P - || src_fmt == AV_PIX_FMT_YUVJ420P || src_fmt == AV_PIX_FMT_YUV420P10LE || src_fmt == AV_PIX_FMT_YUV420P12LE) { plane_params.set_height(plane_params.height()/2); } - TexturePtr u_plane = renderer->CreateTexture(plane_params, f->data[1], f->linesize[1] / px_size); - TexturePtr v_plane = renderer->CreateTexture(plane_params, f->data[2], f->linesize[2] / px_size); + TexturePtr u_plane = p.renderer->CreateTexture(plane_params, f->data[1], f->linesize[1] / px_size); + TexturePtr v_plane = p.renderer->CreateTexture(plane_params, f->data[2], f->linesize[2] / px_size); ShaderJob job; job.Insert(QStringLiteral("y_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(y_plane))); job.Insert(QStringLiteral("u_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(u_plane))); job.Insert(QStringLiteral("v_channel"), NodeValue(NodeValue::kTexture, QVariant::fromValue(v_plane))); job.Insert(QStringLiteral("bits_per_pixel"), NodeValue(NodeValue::kInt, bits_per_pixel)); - job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, full_range)); + job.Insert(QStringLiteral("full_range"), NodeValue(NodeValue::kBoolean, f->color_range == AVCOL_RANGE_JPEG)); const int *yuv_coeffs = sws_getCoefficients(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(f.get()->colorspace)); job.Insert(QStringLiteral("yuv_crv"), NodeValue(NodeValue::kInt, yuv_coeffs[0])); @@ -252,8 +243,8 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration job.Insert(QStringLiteral("yuv_cgv"), NodeValue(NodeValue::kInt, yuv_coeffs[3])); job.Insert(QStringLiteral("yuv_cbu"), NodeValue(NodeValue::kInt, yuv_coeffs[1])); - tex = renderer->CreateTexture(vp); - renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); + tex = p.renderer->CreateTexture(vp); + p.renderer->BlitToTexture(Yuv2RgbShader, job, tex.get(), false); } } } @@ -261,6 +252,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration if (!tex) { // Fallback to software pixel format conversion int r; + r = av_buffersrc_add_frame_flags(buffersrc_ctx_, f.get(), AV_BUFFERSRC_FLAG_KEEP_REF); if (r < 0) { return nullptr; @@ -270,7 +262,7 @@ TexturePtr FFmpegDecoder::RetrieveVideoInternal(Renderer *renderer, const ration return nullptr; } - tex = renderer->CreateTexture(vp, working_frame_->data[0], working_frame_->linesize[0] / vp.GetBytesPerPixel()); + tex = p.renderer->CreateTexture(vp, working_frame_->data[0], working_frame_->linesize[0] / vp.GetBytesPerPixel()); av_frame_unref(working_frame_); } @@ -435,6 +427,7 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, CancelAtom *can stream.set_start_time(avstream->start_time); stream.set_time_base(avstream->time_base); stream.set_duration(avstream->duration); + stream.set_color_range(avstream->codecpar->color_range == AVCOL_RANGE_JPEG ? VideoParams::kColorRangeFull : VideoParams::kColorRangeLimited); // Defaults to false, requires user intervention if incorrect stream.set_premultiplied_alpha(false); @@ -900,7 +893,11 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, CancelAtom *cancel bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params) { - if (params == filter_params_ && filter_graph_ && input_fmt_ == input->format) { + if (params.divider == filter_params_.divider + && params.force_range == filter_params_.force_range + && params.maximum_format == filter_params_.maximum_format + && filter_graph_ + && input_fmt_ == input->format) { // We have an appropriate filter for these parameters, just return true return true; } diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index ad9bc571a..756997098 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -62,7 +62,7 @@ public: protected: virtual bool OpenInternal() override; - virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, CancelAtom *cancelled) override; + virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p) override; virtual bool ConformAudioInternal(const QVector& filenames, const AudioParams ¶ms, CancelAtom *cancelled) override; virtual void CloseInternal() override; diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp index 1a66cbae8..b532d0689 100644 --- a/app/codec/ffmpeg/ffmpegencoder.cpp +++ b/app/codec/ffmpeg/ffmpegencoder.cpp @@ -57,6 +57,11 @@ QStringList FFmpegEncoder::GetPixelFormatsForCodec(ExportCodec::Codec c) const if (codec_info) { for (int i=0; codec_info->pix_fmts[i]!=-1; i++) { + if (FFmpegUtils::ConvertJPEGSpaceToRegularSpace(codec_info->pix_fmts[i]) != codec_info->pix_fmts[i]) { + // This is a deprecated "JPEG" space, skip it + continue; + } + const char* pix_fmt_name = av_get_pix_fmt_name(codec_info->pix_fmts[i]); pix_fmts.append(pix_fmt_name); } @@ -172,7 +177,7 @@ bool FFmpegEncoder::Open() AVFilterContext* range_filter; snprintf(filter_args, FILTER_ARG_SZ, "in_range=full:out_range=%s", - params().video_color_range() == EncodingParams::kYUVJPEG0_255 ? "full" : "limited"); + params().video_params().color_range() == VideoParams::kColorRangeFull ? "full" : "limited"); avfilter_graph_create_filter(&range_filter, avfilter_get_by_name("scale"), "range", filter_args, nullptr, video_scale_ctx_); @@ -610,7 +615,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV codec_ctx->time_base = params().video_params().frame_rate_as_time_base().toAVRational(); codec_ctx->framerate = params().video_params().frame_rate().toAVRational(); codec_ctx->pix_fmt = av_get_pix_fmt(params().video_pix_fmt().toUtf8()); - codec_ctx->color_range = params().video_color_range() == EncodingParams::kYUVJPEG0_255 ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; + codec_ctx->color_range = params().video_params().color_range() == VideoParams::kColorRangeFull ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; if (params().video_params().interlacing() != VideoParams::kInterlaceNone) { // FIXME: I actually don't know what these flags do, the documentation helpfully doesn't diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 7c7dd8878..5593bbbe1 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -119,22 +119,20 @@ bool OIIODecoder::OpenInternal() return OpenImageHandler(stream().filename(), stream().stream()); } -TexturePtr OIIODecoder::RetrieveVideoInternal(Renderer *renderer, const rational &timecode, const RetrieveVideoParams ¶ms, CancelAtom *cancelled) +TexturePtr OIIODecoder::RetrieveVideoInternal(const RetrieveVideoParams &p) { - Q_UNUSED(timecode) - Q_UNUSED(cancelled) - VideoParams vp = GetVideoParamsFromImageSpec(image_->spec()); - vp.set_divider(params.divider); + vp.set_divider(p.divider); - if (!buffer_.is_allocated() || last_params_ != params) { - last_params_ = params; + if (!buffer_.is_allocated() + || last_params_.divider != p.divider) { + last_params_ = p; buffer_.destroy(); buffer_.set_video_params(vp); buffer_.allocate(); - if (params.divider == 1) { + if (p.divider == 1) { // Just upload straight to the buffer image_->read_image(oiio_pix_fmt_, buffer_.data(), OIIO::AutoStride, buffer_.linesize_bytes()); } else { @@ -156,7 +154,7 @@ TexturePtr OIIODecoder::RetrieveVideoInternal(Renderer *renderer, const rational } } - return renderer->CreateTexture(vp, buffer_.data(), buffer_.linesize_pixels()); + return p.renderer->CreateTexture(vp, buffer_.data(), buffer_.linesize_pixels()); } void OIIODecoder::CloseInternal() diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 3cc894eba..f16e33692 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -44,7 +44,7 @@ public: protected: virtual bool OpenInternal() override; - virtual TexturePtr RetrieveVideoInternal(Renderer *renderer, const rational& timecode, const RetrieveVideoParams& params, CancelAtom *cancelled) override; + virtual TexturePtr RetrieveVideoInternal(const RetrieveVideoParams& p) override; virtual void CloseInternal() override; private: diff --git a/app/common/ffmpegutils.cpp b/app/common/ffmpegutils.cpp index c26172d26..565f0269c 100644 --- a/app/common/ffmpegutils.cpp +++ b/app/common/ffmpegutils.cpp @@ -133,6 +133,21 @@ int FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVColorSpace cs) return SWS_CS_DEFAULT; } +AVPixelFormat FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AVPixelFormat f) +{ + switch (f) { + case AV_PIX_FMT_YUVJ420P: return AV_PIX_FMT_YUV420P; + case AV_PIX_FMT_YUVJ422P: return AV_PIX_FMT_YUV422P; + case AV_PIX_FMT_YUVJ444P: return AV_PIX_FMT_YUV444P; + case AV_PIX_FMT_YUVJ440P: return AV_PIX_FMT_YUV440P; + case AV_PIX_FMT_YUVJ411P: return AV_PIX_FMT_YUV411P; + default: + break; + } + + return f; +} + AVPixelFormat FFmpegUtils::GetFFmpegPixelFormat(const VideoParams::Format &pix_fmt, int channel_layout) { if (channel_layout == VideoParams::kRGBChannelCount) { diff --git a/app/common/ffmpegutils.h b/app/common/ffmpegutils.h index 03d1a1008..7d13b8642 100644 --- a/app/common/ffmpegutils.h +++ b/app/common/ffmpegutils.h @@ -66,6 +66,15 @@ public: * convenience function to do this conversion for us? Who knows, but here we are. */ static int GetSwsColorspaceFromAVColorSpace(AVColorSpace cs); + + /** + * @brief Convert "JPEG"/full-range colorspace to its regular counterpart + * + * "JPEG "spaces are deprecated in favor of the regular space and setting `color_range`. For the + * time being, FFmpeg still uses these JPEG spaces, so for simplicity (since we *are* color_range + * aware), we use this function. + */ + static AVPixelFormat ConvertJPEGSpaceToRegularSpace(AVPixelFormat f); }; using AVFramePtr = std::shared_ptr; diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 986d3a169..0968428f8 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -557,6 +557,9 @@ ExportParams ExportDialog::GenerateParams() const if (video_enabled_->isChecked()) { ExportCodec::Codec video_codec = video_tab_->GetSelectedCodec(); + + video_render_params.set_color_range(video_tab_->color_range()); + params.EnableVideo(video_render_params, video_codec); params.set_video_threads(video_tab_->threads()); @@ -568,7 +571,6 @@ ExportParams ExportDialog::GenerateParams() const params.set_color_transform(video_tab_->CurrentOCIOColorSpace()); params.set_video_pix_fmt(video_tab_->pix_fmt()); - params.set_video_color_range(video_tab_->yuv_range()); params.set_video_is_image_sequence(video_tab_->IsImageSequenceSet()); } diff --git a/app/dialog/export/exportadvancedvideodialog.h b/app/dialog/export/exportadvancedvideodialog.h index b6d17214c..f001a476d 100644 --- a/app/dialog/export/exportadvancedvideodialog.h +++ b/app/dialog/export/exportadvancedvideodialog.h @@ -36,12 +36,12 @@ public: pixel_format_combobox_->setCurrentText(s); } - EncodingParams::YUVRange yuv_range() const + VideoParams::ColorRange yuv_range() const { - return static_cast(yuv_color_range_combobox_->currentIndex()); + return static_cast(yuv_color_range_combobox_->currentIndex()); } - void set_yuv_range(EncodingParams::YUVRange i) + void set_yuv_range(VideoParams::ColorRange i) { yuv_color_range_combobox_->setCurrentIndex(i); } diff --git a/app/dialog/export/exportvideotab.cpp b/app/dialog/export/exportvideotab.cpp index 4373eb75f..fd175c8a0 100644 --- a/app/dialog/export/exportvideotab.cpp +++ b/app/dialog/export/exportvideotab.cpp @@ -37,7 +37,7 @@ ExportVideoTab::ExportVideoTab(ColorManager* color_manager, QWidget *parent) : QWidget(parent), color_manager_(color_manager), threads_(0), - yuv_range_(EncodingParams::kYUVDefault) + color_range_(VideoParams::kColorRangeDefault) { QVBoxLayout* outer_layout = new QVBoxLayout(this); @@ -212,12 +212,12 @@ void ExportVideoTab::OpenAdvancedDialog() d.set_threads(threads_); d.set_pix_fmt(pix_fmt_); - d.set_yuv_range(yuv_range_); + d.set_yuv_range(color_range_); if (d.exec() == QDialog::Accepted) { threads_ = d.threads(); pix_fmt_ = d.pix_fmt(); - yuv_range_ = d.yuv_range(); + color_range_ = d.yuv_range(); } } diff --git a/app/dialog/export/exportvideotab.h b/app/dialog/export/exportvideotab.h index 0a83753ee..f6f997ee2 100644 --- a/app/dialog/export/exportvideotab.h +++ b/app/dialog/export/exportvideotab.h @@ -138,9 +138,9 @@ public: return pix_fmt_; } - EncodingParams::YUVRange yuv_range() const + VideoParams::ColorRange color_range() const { - return yuv_range_; + return color_range_; } public slots: @@ -183,7 +183,7 @@ private: int threads_; QString pix_fmt_; - EncodingParams::YUVRange yuv_range_; + VideoParams::ColorRange color_range_; ExportFormat::Format format_; diff --git a/app/dialog/footageproperties/footageproperties.h b/app/dialog/footageproperties/footageproperties.h index aa71fa01e..e25898381 100644 --- a/app/dialog/footageproperties/footageproperties.h +++ b/app/dialog/footageproperties/footageproperties.h @@ -83,11 +83,6 @@ private: */ QStackedWidget* stacked_widget_; - /** - * @brief ComboBox for interlacing setting - */ - QComboBox* interlacing_box; - /** * @brief Media name text field */ diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp index 6fcbdd914..6ed1e486f 100644 --- a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp +++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp @@ -79,6 +79,17 @@ VideoStreamProperties::VideoStreamProperties(Footage *footage, int video_index) video_layout->addWidget(video_color_space_, row, 1); + row++; + + video_layout->addWidget(new QLabel(tr("Color Range:")), row, 0); + + color_range_combo_ = new QComboBox(); + color_range_combo_->addItem(tr("Limited (16-235)"), VideoParams::kColorRangeLimited); + color_range_combo_->addItem(tr("Full (0-255)"), VideoParams::kColorRangeFull); + color_range_combo_->setCurrentIndex(vp.color_range()); + + video_layout->addWidget(color_range_combo_, row, 1); + if (vp.channel_count() == VideoParams::kRGBAChannelCount) { row++; @@ -136,14 +147,16 @@ void VideoStreamProperties::Accept(MultiUndoCommand *parent) if ((video_premultiply_alpha_ && video_premultiply_alpha_->isChecked() != vp.premultiplied_alpha()) || set_colorspace != vp.colorspace() || static_cast(video_interlace_combo_->currentIndex()) != vp.interlacing() - || pixel_aspect_combo_->GetPixelAspectRatio() != vp.pixel_aspect_ratio()) { + || pixel_aspect_combo_->GetPixelAspectRatio() != vp.pixel_aspect_ratio() + || color_range_combo_->currentData().toInt() != vp.color_range()) { parent->add_child(new VideoStreamChangeCommand(footage_, video_index_, video_premultiply_alpha_ ? video_premultiply_alpha_->isChecked() : vp.premultiplied_alpha(), set_colorspace, static_cast(video_interlace_combo_->currentIndex()), - pixel_aspect_combo_->GetPixelAspectRatio())); + pixel_aspect_combo_->GetPixelAspectRatio(), + static_cast(color_range_combo_->currentData().toInt()))); } if (vp.video_type() == VideoParams::kVideoTypeImageSequence) { @@ -181,13 +194,14 @@ VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(Footag bool premultiplied, QString colorspace, VideoParams::Interlacing interlacing, - const rational &pixel_ar) : + const rational &pixel_ar, VideoParams::ColorRange range) : footage_(footage), video_index_(video_index), new_premultiplied_(premultiplied), new_colorspace_(colorspace), new_interlacing_(interlacing), - new_pixel_ar_(pixel_ar) + new_pixel_ar_(pixel_ar), + new_range_(range) { } @@ -204,11 +218,13 @@ void VideoStreamProperties::VideoStreamChangeCommand::redo() old_colorspace_ = vp.colorspace(); old_interlacing_ = vp.interlacing(); old_pixel_ar_ = vp.pixel_aspect_ratio(); + old_range_ = vp.color_range(); vp.set_premultiplied_alpha(new_premultiplied_); vp.set_colorspace(new_colorspace_); vp.set_interlacing(new_interlacing_); vp.set_pixel_aspect_ratio(new_pixel_ar_); + vp.set_color_range(new_range_); footage_->SetVideoParams(vp, video_index_); } @@ -221,6 +237,7 @@ void VideoStreamProperties::VideoStreamChangeCommand::undo() vp.set_colorspace(old_colorspace_); vp.set_interlacing(old_interlacing_); vp.set_pixel_aspect_ratio(old_pixel_ar_); + vp.set_color_range(old_range_); footage_->SetVideoParams(vp, video_index_); } diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.h b/app/dialog/footageproperties/streamproperties/videostreamproperties.h index ea689e2f0..dec410c97 100644 --- a/app/dialog/footageproperties/streamproperties/videostreamproperties.h +++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.h @@ -56,6 +56,11 @@ private: */ QComboBox* video_color_space_; + /** + * @brief Setting for this streams's color range + */ + QComboBox *color_range_combo_; + /** * @brief Setting for video interlacing */ @@ -88,7 +93,8 @@ private: bool premultiplied, QString colorspace, VideoParams::Interlacing interlacing, - const rational& pixel_ar); + const rational& pixel_ar, + VideoParams::ColorRange range); virtual Project* GetRelevantProject() const override; @@ -104,11 +110,13 @@ private: QString new_colorspace_; VideoParams::Interlacing new_interlacing_; rational new_pixel_ar_; + VideoParams::ColorRange new_range_; bool old_premultiplied_; QString old_colorspace_; VideoParams::Interlacing old_interlacing_; rational old_pixel_ar_; + VideoParams::ColorRange old_range_; }; diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 7c89f3496..459e1711f 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -519,6 +519,7 @@ VideoParams Footage::MergeVideoStream(const VideoParams &base, const VideoParams merged.set_colorspace(over.colorspace()); merged.set_premultiplied_alpha(over.premultiplied_alpha()); merged.set_video_type(over.video_type()); + merged.set_color_range(over.color_range()); if (merged.video_type() == VideoParams::kVideoTypeImageSequence) { merged.set_start_time(over.start_time()); merged.set_duration(over.duration()); diff --git a/app/node/project/footage/footagedescription.h b/app/node/project/footage/footagedescription.h index c05849273..42e6a07be 100644 --- a/app/node/project/footage/footagedescription.h +++ b/app/node/project/footage/footagedescription.h @@ -142,7 +142,7 @@ public: } private: - static constexpr unsigned kFootageMetaVersion = 4; + static constexpr unsigned kFootageMetaVersion = 5; QString decoder_; diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index da419eef7..9e8ee169c 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -475,7 +475,14 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ VideoParams tex_params = stream.video_params(); if (tex_params.is_valid()) { - TexturePtr unmanaged_texture = decoder->RetrieveVideo(render_ctx_, (stream_data.video_type() == VideoParams::kVideoTypeVideo) ? input_time : Decoder::kAnyTimecode, p, GetCancelPointer()); + TexturePtr unmanaged_texture; + + p.renderer = render_ctx_; + p.time = (stream_data.video_type() == VideoParams::kVideoTypeVideo) ? input_time : Decoder::kAnyTimecode; + p.cancelled = GetCancelPointer(); + p.force_range = stream_data.color_range(); + + unmanaged_texture = decoder->RetrieveVideo(p); if (unmanaged_texture) { // We convert to our rendering pixel format, since that will always be float-based which diff --git a/app/render/videoparams.cpp b/app/render/videoparams.cpp index f4b341238..239507b98 100644 --- a/app/render/videoparams.cpp +++ b/app/render/videoparams.cpp @@ -270,6 +270,7 @@ void VideoParams::set_defaults_for_footage() premultiplied_alpha_ = false; x_ = 0; y_ = 0; + color_range_ = kColorRangeDefault; } void VideoParams::calculate_square_pixel_width() @@ -374,6 +375,8 @@ void VideoParams::Load(QXmlStreamReader *reader) set_premultiplied_alpha(reader->readElementText().toInt()); } else if (reader->name() == QStringLiteral("colorspace")) { set_colorspace(reader->readElementText()); + } else if (reader->name() == QStringLiteral("colorrange")) { + set_color_range(static_cast(reader->readElementText().toInt())); } else { reader->skipCurrentElement(); } @@ -401,6 +404,7 @@ void VideoParams::Save(QXmlStreamWriter *writer) const writer->writeTextElement(QStringLiteral("duration"), QString::number(duration_)); writer->writeTextElement(QStringLiteral("premultipliedalpha"), QString::number(premultiplied_alpha_)); writer->writeTextElement(QStringLiteral("colorspace"), colorspace_); + writer->writeTextElement(QStringLiteral("colorrange"), QString::number(color_range_)); } } diff --git a/app/render/videoparams.h b/app/render/videoparams.h index 28d8c29fe..e65ad7a93 100644 --- a/app/render/videoparams.h +++ b/app/render/videoparams.h @@ -66,6 +66,14 @@ public: kVideoTypeStill, kVideoTypeImageSequence }; + enum ColorRange + { + kColorRangeLimited, // 16_235 + kColorRangeFull, // 0-255 + + kColorRangeDefault = kColorRangeLimited + }; + VideoParams(); VideoParams(int width, int height, Format format, int nb_channels, @@ -352,6 +360,9 @@ public: colorspace_ = c; } + const ColorRange &color_range() const { return color_range_; } + void set_color_range(const ColorRange &color_range) { color_range_ = color_range; } + int64_t get_time_in_timebase_units(const rational& time) const; void Load(QXmlStreamReader* reader); @@ -398,6 +409,7 @@ private: QString colorspace_; float x_; float y_; + ColorRange color_range_; };