From 6edb38501b1fc63aba38698ce8877f9453074356 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Mon, 19 May 2014 19:21:50 -0700 Subject: [PATCH] Update the YCbCr matrix used by the video display when the header is changed --- src/include/aegisub/video_provider.h | 6 +++ src/threaded_frame_source.cpp | 8 ++-- src/threaded_frame_source.h | 3 ++ src/video_context.cpp | 15 +++++-- src/video_context.h | 3 ++ src/video_provider_avs.cpp | 65 +++++++++++++++------------- src/video_provider_cache.cpp | 5 +++ src/video_provider_dummy.h | 1 + src/video_provider_ffmpegsource.cpp | 30 +++++++++---- src/video_provider_yuv4mpeg.cpp | 1 + 10 files changed, 94 insertions(+), 43 deletions(-) diff --git a/src/include/aegisub/video_provider.h b/src/include/aegisub/video_provider.h index 8e6d05332..c962c1a07 100644 --- a/src/include/aegisub/video_provider.h +++ b/src/include/aegisub/video_provider.h @@ -49,6 +49,12 @@ public: /// Override this method to actually get frames virtual std::shared_ptr GetFrame(int n)=0; + /// Set the YCbCr matrix to the specified one + /// + /// Providers are free to disregard this, and should if the requested + /// matrix makes no sense or the input isn't YCbCr. + virtual void SetColorSpace(std::string const& matrix)=0; + // Override the following methods to get video information: virtual int GetFrameCount() const=0; ///< Get total number of frames virtual int GetWidth() const=0; ///< Returns the video width in pixels diff --git a/src/threaded_frame_source.cpp b/src/threaded_frame_source.cpp index c58f64097..16f3eb880 100644 --- a/src/threaded_frame_source.cpp +++ b/src/threaded_frame_source.cpp @@ -168,12 +168,14 @@ void ThreadedFrameSource::ProcAsync(uint_fast32_t req_version) { std::shared_ptr ThreadedFrameSource::GetFrame(int frame, double time, bool raw) { std::shared_ptr ret; - worker->Sync([&]{ - ret = ProcFrame(frame, time, raw); - }); + worker->Sync([&]{ ret = ProcFrame(frame, time, raw); }); return ret; } +void ThreadedFrameSource::SetColorSpace(std::string const& matrix) { + worker->Async([=] { video_provider->SetColorSpace(matrix); }); +} + wxDEFINE_EVENT(EVT_FRAME_READY, FrameReadyEvent); wxDEFINE_EVENT(EVT_VIDEO_ERROR, VideoProviderErrorEvent); wxDEFINE_EVENT(EVT_SUBTITLES_ERROR, SubtitlesProviderErrorEvent); diff --git a/src/threaded_frame_source.h b/src/threaded_frame_source.h index d8a4c813f..e347f4cd4 100644 --- a/src/threaded_frame_source.h +++ b/src/threaded_frame_source.h @@ -106,6 +106,9 @@ public: /// Get a reference to the video provider this is using VideoProvider *GetVideoProvider() const { return video_provider.get(); } + /// Ask the video provider to change YCbCr matricies + void SetColorSpace(std::string const& matrix); + /// @brief Constructor /// @param videoFileName File to open /// @param parent Event handler to send FrameReady events to diff --git a/src/video_context.cpp b/src/video_context.cpp index 2d099e691..c41a9bcc4 100644 --- a/src/video_context.cpp +++ b/src/video_context.cpp @@ -94,6 +94,7 @@ void VideoContext::Reset() { // Clean up video data video_filename.clear(); + color_matrix.clear(); // Remove provider provider.reset(); @@ -121,13 +122,13 @@ void VideoContext::SetVideo(const agi::fs::path &filename) { provider = agi::make_unique(filename, old_matrix, this, progress); video_provider = provider->GetVideoProvider(); video_filename = filename; + color_matrix = video_provider->GetColorSpace(); + keyframes = video_provider->GetKeyFrames(); + video_fps = video_provider->GetFPS(); bool needs_commit = UpdateVideoProperties(context->ass.get(), video_provider, context->parent); - keyframes = video_provider->GetKeyFrames(); - // Set frame rate - video_fps = video_provider->GetFPS(); if (ovr_fps.IsLoaded()) { int ovr = wxMessageBox(_("You already have timecodes loaded. Would you like to replace them with timecodes from the video file?"), _("Replace timecodes?"), wxYES_NO | wxICON_QUESTION); @@ -186,6 +187,14 @@ void VideoContext::Reload() { void VideoContext::OnSubtitlesCommit(int type, std::set const& changed) { if (!IsLoaded()) return; + if ((type & AssFile::COMMIT_SCRIPTINFO) || type == AssFile::COMMIT_NEW) { + auto new_matrix = context->ass->GetScriptInfo("YCbCr Matrix"); + if (!new_matrix.empty() && new_matrix != color_matrix) { + color_matrix = new_matrix; + provider->SetColorSpace(new_matrix); + } + } + if (changed.empty() || no_amend) provider->LoadSubtitles(context->ass.get()); else diff --git a/src/video_context.h b/src/video_context.h index f4b648312..603d1b98e 100644 --- a/src/video_context.h +++ b/src/video_context.h @@ -98,6 +98,9 @@ class VideoContext final : public wxEvtHandler { /// Filename of currently open video agi::fs::path video_filename; + /// Last seen script color matrix + std::string color_matrix; + /// List of frame numbers which are keyframes std::vector keyframes; diff --git a/src/video_provider_avs.cpp b/src/video_provider_avs.cpp index 9afeeb3c2..71825a742 100644 --- a/src/video_provider_avs.cpp +++ b/src/video_provider_avs.cpp @@ -67,16 +67,23 @@ class AvisynthVideoProvider: public VideoProvider { std::string colorspace; std::string real_colorspace; + AVSValue source_clip; PClip RGB32Video; VideoInfo vi; AVSValue Open(agi::fs::path const& filename); + void Init(std::string const& matrix); public: AvisynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix); std::shared_ptr GetFrame(int n); + void SetColorSpace(std::string const& matrix) override { + // Can't really do anything if this fails + try { Init(matrix); } catch (AvisynthError const&) { } + } + int GetFrameCount() const override { return vi.num_frames; } agi::vfr::Framerate GetFPS() const override { return fps; } int GetWidth() const override { return vi.width; } @@ -171,41 +178,41 @@ file_exit: #endif try { - auto script = Open(filename); - - // Check if video was loaded properly - if (!script.IsClip() || !script.AsClip()->GetVideoInfo().HasVideo()) - throw VideoNotSupported("No usable video found"); - - vi = script.AsClip()->GetVideoInfo(); - if (vi.IsRGB()) - real_colorspace = colorspace = "None"; - else { - /// @todo maybe read ColorMatrix hints for d2v files? - AVSValue args[2] = { script, "Rec601" }; - bool force_bt601 = OPT_GET("Video/Force BT.601")->GetBool() || colormatrix == "TV.601"; - bool bt709 = vi.width > 1024 || vi.height >= 600; - if (bt709 && (!force_bt601 || colormatrix == "TV.709")) { - args[1] = "Rec709"; - real_colorspace = colorspace = "TV.709"; - } - else { - colorspace = "TV.601"; - real_colorspace = bt709 ? "TV.709" : "TV.601"; - } - const char *argnames[2] = { 0, "matrix" }; - script = avs.GetEnv()->Invoke("ConvertToRGB32", AVSValue(args, 2), argnames); - } - - RGB32Video = avs.GetEnv()->Invoke("Cache", script).AsClip(); - vi = RGB32Video->GetVideoInfo(); - fps = (double)vi.fps_numerator / vi.fps_denominator; + source_clip = Open(filename); + Init(colormatrix); } catch (AvisynthError const& err) { throw VideoOpenError("Avisynth error: " + std::string(err.msg)); } } +void AvisynthVideoProvider::Init(std::string const& colormatrix) { + auto script = source_clip; + vi = script.AsClip()->GetVideoInfo(); + if (vi.IsRGB()) + real_colorspace = colorspace = "None"; + else { + /// @todo maybe read ColorMatrix hints for d2v files? + AVSValue args[2] = { script, "Rec601" }; + bool force_bt601 = OPT_GET("Video/Force BT.601")->GetBool() || colormatrix == "TV.601"; + bool bt709 = vi.width > 1024 || vi.height >= 600; + if (bt709 && (!force_bt601 || colormatrix == "TV.709")) { + args[1] = "Rec709"; + real_colorspace = colorspace = "TV.709"; + } + else { + colorspace = "TV.601"; + real_colorspace = bt709 ? "TV.709" : "TV.601"; + } + const char *argnames[2] = { 0, "matrix" }; + script = avs.GetEnv()->Invoke("ConvertToRGB32", AVSValue(args, 2), argnames); + } + + RGB32Video = avs.GetEnv()->Invoke("Cache", script).AsClip(); + vi = RGB32Video->GetVideoInfo(); + fps = (double)vi.fps_numerator / vi.fps_denominator; +} + AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) { IScriptEnvironment *env = avs.GetEnv(); char *videoFilename = env->SaveString(agi::fs::ShortName(filename).c_str()); diff --git a/src/video_provider_cache.cpp b/src/video_provider_cache.cpp index 0633b98a9..8e31540a4 100644 --- a/src/video_provider_cache.cpp +++ b/src/video_provider_cache.cpp @@ -59,6 +59,11 @@ public: std::shared_ptr GetFrame(int n) override; + void SetColorSpace(std::string const& m) override { + cache.clear(); + return master->SetColorSpace(m); + } + int GetFrameCount() const override { return master->GetFrameCount(); } int GetWidth() const override { return master->GetWidth(); } int GetHeight() const override { return master->GetHeight(); } diff --git a/src/video_provider_dummy.h b/src/video_provider_dummy.h index 8826e6594..4bcc0d7a1 100644 --- a/src/video_provider_dummy.h +++ b/src/video_provider_dummy.h @@ -65,6 +65,7 @@ public: static std::string MakeFilename(double fps, int frames, int width, int height, agi::Color colour, bool pattern); std::shared_ptr GetFrame(int n) override; + void SetColorSpace(std::string const&) override { } int GetFrameCount() const override { return framecount; } int GetWidth() const override { return width; } diff --git a/src/video_provider_ffmpegsource.cpp b/src/video_provider_ffmpegsource.cpp index 49f0fc9a5..9254c273e 100644 --- a/src/video_provider_ffmpegsource.cpp +++ b/src/video_provider_ffmpegsource.cpp @@ -58,6 +58,8 @@ class FFmpegSourceVideoProvider final : public VideoProvider, FFmpegSourceProvid int Width = -1; ///< width in pixels int Height = -1; ///< height in pixels + int CS = -1; ///< Reported colorspace of first frame + int CR = -1; ///< Reported colorrange of first frame double DAR; ///< display aspect ratio std::vector KeyFramesList; ///< list of keyframes agi::vfr::Framerate Timecodes; ///< vfr object @@ -74,6 +76,19 @@ public: std::shared_ptr GetFrame(int n) override; + void SetColorSpace(std::string const& matrix) override { +#if FFMS_VERSION >= ((2 << 24) | (17 << 16) | (1 << 8) | 0) + if (matrix == ColorSpace) return; + if (matrix == RealColorSpace) + FFMS_SetInputFormatV(VideoSource, CS, CR, FFMS_GetPixFmt(""), nullptr); + else if (matrix == "TV.601") + FFMS_SetInputFormatV(VideoSource, FFMS_CS_BT470BG, CR, FFMS_GetPixFmt(""), nullptr); + else + return; + ColorSpace = matrix; +#endif + } + int GetFrameCount() const override { return VideoInfo->NumFrames; } int GetWidth() const override { return Width; } int GetHeight() const override { return Height; } @@ -225,25 +240,24 @@ void FFmpegSourceVideoProvider::LoadVideo(agi::fs::path const& filename, std::st else DAR = double(Width) / Height; - auto CS = TempFrame->ColorSpace; + CS = TempFrame->ColorSpace; + CR = TempFrame->ColorRange; + if (CS == FFMS_CS_UNSPECIFIED) CS = Width > 1024 || Height >= 600 ? FFMS_CS_BT709 : FFMS_CS_BT470BG; - RealColorSpace = ColorSpace = colormatrix_description(CS, TempFrame->ColorRange); + RealColorSpace = ColorSpace = colormatrix_description(CS, CR); #if FFMS_VERSION >= ((2 << 24) | (17 << 16) | (1 << 8) | 0) if (CS != FFMS_CS_RGB && CS != FFMS_CS_BT470BG && ColorSpace != colormatrix && (colormatrix == "TV.601" || OPT_GET("Video/Force BT.601")->GetBool())) { - if (FFMS_SetInputFormatV(VideoSource, FFMS_CS_BT470BG, TempFrame->ColorRange, FFMS_GetPixFmt(""), &ErrInfo)) + if (FFMS_SetInputFormatV(VideoSource, FFMS_CS_BT470BG, CR, FFMS_GetPixFmt(""), &ErrInfo)) throw VideoOpenError(std::string("Failed to set input format: ") + ErrInfo.Buffer); - - CS = FFMS_CS_BT470BG; - ColorSpace = colormatrix_description(CS, TempFrame->ColorRange); + ColorSpace = colormatrix_description(FFMS_CS_BT470BG, CR); } #endif const int TargetFormat[] = { FFMS_GetPixFmt("bgra"), -1 }; - if (FFMS_SetOutputFormatV2(VideoSource, TargetFormat, Width, Height, FFMS_RESIZER_BICUBIC, &ErrInfo)) { + if (FFMS_SetOutputFormatV2(VideoSource, TargetFormat, Width, Height, FFMS_RESIZER_BICUBIC, &ErrInfo)) throw VideoOpenError(std::string("Failed to set output format: ") + ErrInfo.Buffer); - } // get frame info data FFMS_Track *FrameData = FFMS_GetTrackFromVideo(VideoSource); diff --git a/src/video_provider_yuv4mpeg.cpp b/src/video_provider_yuv4mpeg.cpp index 6f0d16845..d254e84ed 100644 --- a/src/video_provider_yuv4mpeg.cpp +++ b/src/video_provider_yuv4mpeg.cpp @@ -147,6 +147,7 @@ public: YUV4MPEGVideoProvider(agi::fs::path const& filename); std::shared_ptr GetFrame(int n) override; + void SetColorSpace(std::string const&) override { } int GetFrameCount() const override { return num_frames; } int GetWidth() const override { return w; }