Add color matrix conversion to the resolution resampler

This commit is contained in:
Thomas Goyne 2014-05-19 16:47:54 -07:00
parent 7a06e08ad0
commit ad33fdb109
14 changed files with 184 additions and 35 deletions

View File

@ -17,6 +17,8 @@
#include <array>
#include <cstdint>
#include <libaegisub/color.h>
namespace agi {
enum class ycbcr_matrix {
bt601,
@ -84,6 +86,11 @@ public:
return to_uint8_t(prod(from_ycbcr,
add(add(prod(to_ycbcr, input), shift_to), shift_from)));
}
Color rgb_to_rgb(Color c) const {
auto arr = rgb_to_rgb(std::array<uint8_t, 3>{{c.r, c.g, c.b}});
return Color{arr[0], arr[1], arr[2]};
}
};
}

View File

@ -294,10 +294,10 @@ static void load_protos() {
proto[++i].Set("\\fr", VariableDataType::FLOAT); // \fr<degrees>
proto[++i].Set("\\fax", VariableDataType::FLOAT); // \fax<factor>
proto[++i].Set("\\fay", VariableDataType::FLOAT); // \fay<factor>
proto[++i].Set("\\1c", VariableDataType::TEXT); // \1c&H<bbggrr>&
proto[++i].Set("\\2c", VariableDataType::TEXT); // \2c&H<bbggrr>&
proto[++i].Set("\\3c", VariableDataType::TEXT); // \3c&H<bbggrr>&
proto[++i].Set("\\4c", VariableDataType::TEXT); // \4c&H<bbggrr>&
proto[++i].Set("\\1c", VariableDataType::TEXT, AssParameterClass::COLOR); // \1c&H<bbggrr>&
proto[++i].Set("\\2c", VariableDataType::TEXT, AssParameterClass::COLOR); // \2c&H<bbggrr>&
proto[++i].Set("\\3c", VariableDataType::TEXT, AssParameterClass::COLOR); // \3c&H<bbggrr>&
proto[++i].Set("\\4c", VariableDataType::TEXT, AssParameterClass::COLOR); // \4c&H<bbggrr>&
proto[++i].Set("\\1a", VariableDataType::TEXT, AssParameterClass::ALPHA); // \1a&H<aa>&
proto[++i].Set("\\2a", VariableDataType::TEXT, AssParameterClass::ALPHA); // \2a&H<aa>&
proto[++i].Set("\\3a", VariableDataType::TEXT, AssParameterClass::ALPHA); // \3a&H<aa>&
@ -312,7 +312,7 @@ static void load_protos() {
proto[++i].Set("\\fs-", VariableDataType::FLOAT); // \fs-<size>
proto[++i].Set("\\fs", VariableDataType::FLOAT, AssParameterClass::ABSOLUTE_SIZE); // \fs<size>
proto[++i].Set("\\an", VariableDataType::INT); // \an<alignment>
proto[++i].Set("\\c", VariableDataType::TEXT); // \c&H<bbggrr>&
proto[++i].Set("\\c", VariableDataType::TEXT, AssParameterClass::COLOR); // \c&H<bbggrr>&
proto[++i].Set("\\b", VariableDataType::INT); // \b<0/1/weight>
proto[++i].Set("\\i", VariableDataType::BOOL); // \i<0/1>
proto[++i].Set("\\u", VariableDataType::BOOL); // \u<0/1>

View File

@ -49,7 +49,8 @@ enum class AssParameterClass {
RELATIVE_TIME_END,
KARAOKE,
DRAWING,
ALPHA
ALPHA,
COLOR
};
enum class VariableDataType {

View File

@ -39,6 +39,7 @@
#include "help_button.h"
#include "include/aegisub/context.h"
#include "libresrc/libresrc.h"
#include "resolution_resampler.h"
#include "validators.h"
#include "video_context.h"
@ -90,15 +91,8 @@ DialogProperties::DialogProperties(agi::Context *c)
res_sizer->Add(ResY, 1, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
res_sizer->Add(FromVideo, 1, 0, 0);
wxString matricies[] = {
"None",
"TV.601", "PC.601",
"TV.709", "PC.709",
"TV.FCC", "PC.FCC",
"TV.240M", "PC.240M"
};
YCbCrMatrix = new wxComboBox(this, -1, c->ass->GetScriptInfo("YCbCr Matrix"),
wxDefaultPosition, wxDefaultSize, boost::size(matricies), matricies, wxCB_READONLY);
wxDefaultPosition, wxDefaultSize, to_wx(MatrixNames()), wxCB_READONLY);
auto matrix_sizer = new wxBoxSizer(wxHORIZONTAL);
matrix_sizer->Add(new wxStaticText(this, -1, "YCbCr Matrix:"), wxSizerFlags().Center());

View File

@ -20,8 +20,10 @@
#include "dialog_resample.h"
#include "ass_file.h"
#include "include/aegisub/context.h"
#include "compat.h"
#include "help_button.h"
#include "include/aegisub/context.h"
#include "include/aegisub/video_provider.h"
#include "libresrc/libresrc.h"
#include "resolution_resampler.h"
#include "validators.h"
@ -29,6 +31,7 @@
#include <boost/range/size.hpp>
#include <wx/checkbox.h>
#include <wx/combobox.h>
#include <wx/sizer.h>
#include <wx/spinctrl.h>
#include <wx/statbox.h>
@ -52,14 +55,18 @@ DialogResample::DialogResample(agi::Context *c, ResampleSettings &settings)
c->ass->GetResolution(script_w, script_h);
settings.source_x = script_w;
settings.source_y = script_h;
settings.source_matrix = script_mat = MatrixFromString(c->ass->GetScriptInfo("YCbCr Matrix"));
if (c->videoController->IsLoaded()) {
settings.dest_x = video_w = c->videoController->GetWidth();
settings.dest_y = video_h = c->videoController->GetHeight();
settings.dest_matrix = video_mat = MatrixFromString(c->videoController->GetProvider()->GetRealColorSpace());
}
else {
settings.dest_x = script_w;
settings.dest_y = script_h;
settings.dest_matrix = script_mat;
video_mat = YCbCrMatrix::rgb;
}
// Create all controls and set validators
@ -76,13 +83,19 @@ DialogResample::DialogResample(agi::Context *c, ResampleSettings &settings)
source_x = new wxSpinCtrl(this, -1, "", wxDefaultPosition, wxSize(50, -1), wxSP_ARROW_KEYS, 1, INT_MAX);
source_y = new wxSpinCtrl(this, -1, "", wxDefaultPosition, wxSize(50, -1), wxSP_ARROW_KEYS, 1, INT_MAX);
source_matrix = new wxComboBox(this, -1, "", wxDefaultPosition,
wxDefaultSize, to_wx(MatrixNames()), wxCB_READONLY);
dest_x = new wxSpinCtrl(this, -1, "", wxDefaultPosition, wxSize(50, -1), wxSP_ARROW_KEYS, 1, INT_MAX);
dest_y = new wxSpinCtrl(this, -1, "", wxDefaultPosition, wxSize(50, -1), wxSP_ARROW_KEYS, 1, INT_MAX);
dest_matrix = new wxComboBox(this, -1, "", wxDefaultPosition, wxDefaultSize,
to_wx(MatrixNames()), wxCB_READONLY);
source_x->SetValidator(wxGenericValidator(&settings.source_x));
source_y->SetValidator(wxGenericValidator(&settings.source_y));
source_matrix->SetValidator(MakeEnumBinder(&settings.source_matrix));
dest_x->SetValidator(wxGenericValidator(&settings.dest_x));
dest_y->SetValidator(wxGenericValidator(&settings.dest_y));
dest_matrix->SetValidator(MakeEnumBinder(&settings.dest_matrix));
from_video = new wxButton(this, -1, _("From &video"));
from_video->Enable(false);
@ -108,25 +121,38 @@ DialogResample::DialogResample(agi::Context *c, ResampleSettings &settings)
auto margin_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Margin offset"));
margin_box->Add(margin_sizer, wxSizerFlags(1).Expand().Border(wxBOTTOM));
auto source_res_sizer = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Source Resolution"));
auto source_res_sizer = new wxBoxSizer(wxHORIZONTAL);
source_res_sizer->Add(source_x, wxSizerFlags(1).Border(wxRIGHT).Align(wxALIGN_CENTER_VERTICAL));
source_res_sizer->Add(new wxStaticText(this, -1, _("x")), wxSizerFlags().Center().Border(wxRIGHT));
source_res_sizer->Add(source_y, wxSizerFlags(1).Border(wxRIGHT).Align(wxALIGN_CENTER_VERTICAL));
source_res_sizer->Add(from_script, wxSizerFlags(1));
auto source_matrix_sizer = new wxBoxSizer(wxHORIZONTAL);
source_matrix_sizer->Add(new wxStaticText(this, -1, _("YCbCr Matrix:")), wxSizerFlags().Border(wxRIGHT).Center());
source_matrix_sizer->Add(source_matrix, wxSizerFlags(1).Center().Right());
auto source_res_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Source Resolution"));
source_res_box->Add(source_res_sizer, wxSizerFlags(1).Expand().Border(wxBOTTOM));
source_res_box->Add(source_matrix_sizer, wxSizerFlags(1).Expand());
auto dest_res_sizer = new wxBoxSizer(wxHORIZONTAL);
dest_res_sizer->Add(dest_x, wxSizerFlags(1).Border(wxRIGHT).Align(wxALIGN_CENTER_VERTICAL));
dest_res_sizer->Add(new wxStaticText(this, -1, _("x")), wxSizerFlags().Center().Border(wxRIGHT));
dest_res_sizer->Add(dest_y, wxSizerFlags(1).Border(wxRIGHT).Align(wxALIGN_CENTER_VERTICAL));
dest_res_sizer->Add(from_video, wxSizerFlags(1));
auto dest_matrix_sizer = new wxBoxSizer(wxHORIZONTAL);
dest_matrix_sizer->Add(new wxStaticText(this, -1, _("YCbCr Matrix:")), wxSizerFlags().Border(wxRIGHT).Center());
dest_matrix_sizer->Add(dest_matrix, wxSizerFlags(1).Center().Right());
auto dest_res_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Destination Resolution"));
dest_res_box->Add(dest_res_sizer, wxSizerFlags(1).Expand().Border(wxBOTTOM));
dest_res_box->Add(dest_matrix_sizer, wxSizerFlags(1).Expand());
auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(source_res_sizer, wxSizerFlags(0).Expand().Border());
main_sizer->Add(dest_res_box, wxSizerFlags(0).Expand().Border());
main_sizer->Add(ar_mode, wxSizerFlags(0).Expand().Border());
main_sizer->Add(source_res_box, wxSizerFlags().Expand().Border());
main_sizer->Add(dest_res_box, wxSizerFlags().Expand().Border());
main_sizer->Add(ar_mode, wxSizerFlags().Expand().Border());
main_sizer->Add(margin_box, wxSizerFlags(1).Expand().Border());
main_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxHELP), wxSizerFlags().Expand().Border(wxALL & ~wxTOP));
SetSizerAndFit(main_sizer);
@ -150,11 +176,13 @@ DialogResample::DialogResample(agi::Context *c, ResampleSettings &settings)
void DialogResample::SetDestFromVideo(wxCommandEvent &) {
dest_x->SetValue(video_w);
dest_y->SetValue(video_h);
dest_matrix->SetSelection((int)video_mat);
}
void DialogResample::SetSourceFromScript(wxCommandEvent&) {
source_x->SetValue(script_w);
source_y->SetValue(script_h);
source_matrix->SetSelection((int)script_mat);
}
void DialogResample::UpdateButtons() {

View File

@ -22,8 +22,10 @@
namespace agi { struct Context; }
class AssFile;
class wxCheckBox;
class wxComboBox;
class wxRadioBox;
class wxSpinCtrl;
enum class YCbCrMatrix : int;
struct ResampleSettings;
/// @class DialogResample
@ -35,13 +37,17 @@ class DialogResample final : public wxDialog {
int script_w;
int script_h;
YCbCrMatrix script_mat;
int video_w = 0;
int video_h = 0;
YCbCrMatrix video_mat;
wxSpinCtrl *source_x;
wxSpinCtrl *source_y;
wxSpinCtrl *dest_x;
wxSpinCtrl *dest_y;
wxComboBox *source_matrix;
wxComboBox *dest_matrix;
wxCheckBox *symmetrical;
wxRadioBox *ar_mode;
wxSpinCtrl *margin_ctrl[4];

View File

@ -136,7 +136,8 @@ bool UpdateVideoProperties(AssFile *file, const VideoProvider *new_provider, wxW
ResampleResolution(file, {
{0, 0, 0, 0},
sx, sy, vx, vy,
ResampleARMode::Stretch
ResampleARMode::Stretch,
YCbCrMatrix::rgb, YCbCrMatrix::rgb
});
return true;
}
@ -149,7 +150,8 @@ bool UpdateVideoProperties(AssFile *file, const VideoProvider *new_provider, wxW
ResampleResolution(file, {
{0, 0, 0, 0},
sx, sy, vx, vy,
static_cast<ResampleARMode>(res - FIX_RESAMPLE)
static_cast<ResampleARMode>(res - FIX_RESAMPLE),
YCbCrMatrix::rgb, YCbCrMatrix::rgb
});
return true;
}

View File

@ -61,6 +61,7 @@ public:
/// @return A string describing the source colorspace or "None" if it is
/// unknown or meaningless
virtual std::string GetColorSpace() const = 0;
virtual std::string GetRealColorSpace() const { return GetColorSpace(); }
/// @brief Use this to set any post-loading warnings, such as "being loaded with unreliable seeking"
virtual std::string GetWarning() const { return ""; }

View File

@ -24,6 +24,7 @@
#include <libaegisub/of_type_adaptor.h>
#include <libaegisub/split.h>
#include <libaegisub/util.h>
#include <libaegisub/ycbcr_conv.h>
#include <algorithm>
#include <boost/algorithm/string/predicate.hpp>
@ -38,6 +39,30 @@ enum {
BOTTOM = 3
};
static const std::string names[] = {
"None",
"TV.601", "PC.601",
"TV.709", "PC.709",
"TV.FCC", "PC.FCC",
"TV.240M", "PC.240M"
};
YCbCrMatrix MatrixFromString(std::string const& str) {
if (str.empty()) return YCbCrMatrix::tv_601;
auto pos = std::find(std::begin(names), std::end(names), str);
if (pos == std::end(names))
return YCbCrMatrix::rgb;
return static_cast<YCbCrMatrix>(std::distance(std::begin(names), pos));
}
std::string MatrixToString(YCbCrMatrix mat) {
return names[static_cast<int>(mat)];
}
std::vector<std::string> MatrixNames() {
return {std::begin(names), std::end(names)};
}
namespace {
std::string transform_drawing(std::string const& drawing, int shift_x, int shift_y, double scale_x, double scale_y) {
bool is_x = true;
@ -76,6 +101,8 @@ namespace {
double rx;
double ry;
double ar;
agi::ycbcr_converter conv;
bool convert_colors;
};
void resample_tags(std::string const& name, AssOverrideParameter *cur, void *ud) {
@ -113,6 +140,11 @@ namespace {
return;
}
case AssParameterClass::COLOR:
if (state->convert_colors)
cur->Set<std::string>(state->conv.rgb_to_rgb(agi::Color{cur->Get<std::string>()}).GetAssOverrideFormatted());
return;
default:
return;
}
@ -152,8 +184,42 @@ namespace {
style.scalex *= state->ar;
for (int i = 0; i < 3; i++)
style.Margin[i] = int((style.Margin[i] + state->margin[i]) * (i < 2 ? state->rx : state->ry) + 0.5);
if (state->convert_colors) {
style.primary = state->conv.rgb_to_rgb(style.primary);
style.secondary = state->conv.rgb_to_rgb(style.secondary);
style.outline = state->conv.rgb_to_rgb(style.outline);
style.shadow = state->conv.rgb_to_rgb(style.shadow);
}
style.UpdateData();
}
agi::ycbcr_matrix matrix(YCbCrMatrix mat) {
switch (mat) {
case YCbCrMatrix::rgb: return agi::ycbcr_matrix::bt601;
case YCbCrMatrix::tv_601: case YCbCrMatrix::pc_601: return agi::ycbcr_matrix::bt601;
case YCbCrMatrix::tv_709: case YCbCrMatrix::pc_709: return agi::ycbcr_matrix::bt709;
case YCbCrMatrix::tv_fcc: case YCbCrMatrix::pc_fcc: return agi::ycbcr_matrix::fcc;
case YCbCrMatrix::tv_240m: case YCbCrMatrix::pc_240m: return agi::ycbcr_matrix::smpte_240m;
}
throw agi::InternalError("Invalid matrix", nullptr);
}
agi::ycbcr_range range(YCbCrMatrix mat) {
switch (mat) {
case YCbCrMatrix::rgb:
case YCbCrMatrix::tv_601:
case YCbCrMatrix::tv_709:
case YCbCrMatrix::tv_fcc:
case YCbCrMatrix::tv_240m:
return agi::ycbcr_range::tv;
case YCbCrMatrix::pc_601:
case YCbCrMatrix::pc_709:
case YCbCrMatrix::pc_fcc:
case YCbCrMatrix::pc_240m:
return agi::ycbcr_range::pc;
}
throw agi::InternalError("Invalid matrix", nullptr);
}
}
void ResampleResolution(AssFile *ass, ResampleSettings settings) {
@ -191,11 +257,23 @@ void ResampleResolution(AssFile *ass, ResampleSettings settings) {
settings.source_x += settings.margin[LEFT] + settings.margin[RIGHT];
settings.source_y += settings.margin[TOP] + settings.margin[BOTTOM];
bool resample_colors =
settings.source_matrix != settings.dest_matrix &&
settings.source_matrix != YCbCrMatrix::rgb &&
settings.dest_matrix != YCbCrMatrix::rgb;
resample_state state = {
settings.margin,
double(settings.dest_x) / double(settings.source_x),
double(settings.dest_y) / double(settings.source_y),
horizontal_stretch
horizontal_stretch,
agi::ycbcr_converter{
matrix(settings.source_matrix),
range(settings.source_matrix),
matrix(settings.dest_matrix),
range(settings.dest_matrix),
},
resample_colors
};
for (auto& line : ass->Styles)
@ -205,6 +283,7 @@ void ResampleResolution(AssFile *ass, ResampleSettings settings) {
ass->SetScriptInfo("PlayResX", std::to_string(settings.dest_x));
ass->SetScriptInfo("PlayResY", std::to_string(settings.dest_y));
ass->SetScriptInfo("YCbCr Matrix", MatrixToString(settings.dest_matrix));
ass->Commit(_("resolution resampling"), AssFile::COMMIT_SCRIPTINFO | AssFile::COMMIT_DIAG_FULL);
}

View File

@ -14,6 +14,9 @@
//
// Aegisub Project http://www.aegisub.org/
#include <string>
#include <vector>
class AssFile;
enum class ResampleARMode {
@ -23,6 +26,22 @@ enum class ResampleARMode {
Manual
};
enum class YCbCrMatrix : int {
rgb,
tv_601,
pc_601,
tv_709,
pc_709,
tv_fcc,
pc_fcc,
tv_240m,
pc_240m
};
YCbCrMatrix MatrixFromString(std::string const& str);
std::string MatrixToString(YCbCrMatrix mat);
std::vector<std::string> MatrixNames();
/// Configuration parameters for a resample
struct ResampleSettings {
int margin[4]; ///< Amount to add to each margin
@ -31,6 +50,8 @@ struct ResampleSettings {
int dest_x; ///< New X resolution
int dest_y; ///< New Y resolution
ResampleARMode ar_mode; ///< What to do if the old AR and new AR don't match
YCbCrMatrix source_matrix;
YCbCrMatrix dest_matrix;
};
/// Resample the subtitles in the project

View File

@ -79,7 +79,9 @@ class EnumBinder final : public wxValidator {
bool Validate(wxWindow *) override { return true; }
bool TransferFromWindow() override {
if (wxRadioBox *rb = dynamic_cast<wxRadioBox*>(GetWindow()))
if (auto rb = dynamic_cast<wxRadioBox*>(GetWindow()))
*value = static_cast<T>(rb->GetSelection());
else if (auto rb = dynamic_cast<wxComboBox*>(GetWindow()))
*value = static_cast<T>(rb->GetSelection());
else
throw agi::InternalError("Control type not supported by EnumBinder", nullptr);
@ -87,8 +89,10 @@ class EnumBinder final : public wxValidator {
}
bool TransferToWindow() override {
if (wxRadioBox *rb = dynamic_cast<wxRadioBox*>(GetWindow()))
if (auto rb = dynamic_cast<wxRadioBox*>(GetWindow()))
rb->SetSelection(static_cast<int>(*value));
else if (auto cb = dynamic_cast<wxComboBox*>(GetWindow()))
cb->SetSelection(static_cast<int>(*value));
else
throw agi::InternalError("Control type not supported by EnumBinder", nullptr);
return true;

View File

@ -65,6 +65,7 @@ class AvisynthVideoProvider: public VideoProvider {
std::vector<int> keyframes;
std::string warning;
std::string colorspace;
std::string real_colorspace;
PClip RGB32Video;
VideoInfo vi;
@ -85,6 +86,7 @@ public:
std::string GetWarning() const override { return warning; }
std::string GetDecoderName() const override { return decoder_name; }
std::string GetColorSpace() const override { return colorspace; }
std::string GetRealColorSpace() const override { return real_colorspace; }
};
AvisynthVideoProvider::AvisynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix) {
@ -176,22 +178,24 @@ file_exit:
throw VideoNotSupported("No usable video found");
vi = script.AsClip()->GetVideoInfo();
if (!vi.IsRGB()) {
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";
colorspace = "TV.709";
real_colorspace = colorspace = "TV.709";
}
else
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);
}
else
colorspace = "None";
RGB32Video = avs.GetEnv()->Invoke("Cache", script).AsClip();
vi = RGB32Video->GetVideoInfo();

View File

@ -68,6 +68,7 @@ public:
std::string GetWarning() const override { return master->GetWarning(); }
std::string GetDecoderName() const override { return master->GetDecoderName(); }
std::string GetColorSpace() const override { return master->GetColorSpace(); }
std::string GetRealColorSpace() const override { return master->GetRealColorSpace(); }
};
std::shared_ptr<VideoFrame> VideoProviderCache::GetFrame(int n) {

View File

@ -62,6 +62,7 @@ class FFmpegSourceVideoProvider final : public VideoProvider, FFmpegSourceProvid
std::vector<int> KeyFramesList; ///< list of keyframes
agi::vfr::Framerate Timecodes; ///< vfr object
std::string ColorSpace; ///< Colorspace name
std::string RealColorSpace; ///< Colorspace name
char FFMSErrMsg[1024]; ///< FFMS error message
FFMS_ErrorInfo ErrInfo; ///< FFMS error codes/messages
@ -79,6 +80,7 @@ public:
double GetDAR() const override { return DAR; }
agi::vfr::Framerate GetFPS() const override { return Timecodes; }
std::string GetColorSpace() const override { return ColorSpace; }
std::string GetRealColorSpace() const override { return RealColorSpace; }
std::vector<int> GetKeyFrames() const override { return KeyFramesList; };
std::string GetDecoderName() const override { return "FFmpegSource"; }
bool WantsCaching() const override { return true; }
@ -91,7 +93,6 @@ std::string colormatrix_description(int cs, int cr) {
switch (cs) {
case FFMS_CS_RGB:
return "None";
break;
case FFMS_CS_BT709:
return str + ".709";
case FFMS_CS_FCC:
@ -227,7 +228,7 @@ void FFmpegSourceVideoProvider::LoadVideo(agi::fs::path const& filename, std::st
auto CS = TempFrame->ColorSpace;
if (CS == FFMS_CS_UNSPECIFIED)
CS = Width > 1024 || Height >= 600 ? FFMS_CS_BT709 : FFMS_CS_BT470BG;
ColorSpace = colormatrix_description(CS, TempFrame->ColorRange);
RealColorSpace = ColorSpace = colormatrix_description(CS, TempFrame->ColorRange);
#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())) {