Skip to content

Commit 5eb5d1a

Browse files
committed
Allow fractional frame rates in dummy video
1 parent 028f9ab commit 5eb5d1a

File tree

4 files changed

+68
-26
lines changed

4 files changed

+68
-26
lines changed

src/dialog_dummy_video.cpp

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
// Aegisub Project http://www.aegisub.org/
1616

1717
#include "colour_button.h"
18+
#include "compat.h"
1819
#include "format.h"
1920
#include "help_button.h"
2021
#include "libresrc/libresrc.h"
@@ -40,7 +41,7 @@ namespace {
4041
struct DialogDummyVideo {
4142
wxDialog d;
4243

43-
double fps = OPT_GET("Video/Dummy/FPS")->GetDouble();
44+
wxString fps = OPT_GET("Video/Dummy/FPS String")->GetString();
4445
int width = OPT_GET("Video/Dummy/Last/Width")->GetInt();
4546
int height = OPT_GET("Video/Dummy/Last/Height")->GetInt();
4647
int length = OPT_GET("Video/Dummy/Last/Length")->GetInt();
@@ -54,7 +55,7 @@ struct DialogDummyVideo {
5455
void AddCtrl(wxString const& label, T *ctrl);
5556

5657
void OnResolutionShortcut(wxCommandEvent &evt);
57-
void UpdateLengthDisplay();
58+
bool UpdateLengthDisplay();
5859

5960
DialogDummyVideo(wxWindow *parent);
6061
};
@@ -85,10 +86,6 @@ wxSpinCtrl *spin_ctrl(wxWindow *parent, int min, int max, int *value) {
8586
return ctrl;
8687
}
8788

88-
wxControl *spin_ctrl(wxWindow *parent, double min, double max, double *value) {
89-
return new wxTextCtrl(parent, -1, "", wxDefaultPosition, wxSize(50, -1), 0, DoubleValidator(value, min, max));
90-
}
91-
9289
wxComboBox *resolution_shortcuts(wxWindow *parent, int width, int height) {
9390
wxComboBox *ctrl = new wxComboBox(parent, -1, "", wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY);
9491

@@ -120,7 +117,9 @@ DialogDummyVideo::DialogDummyVideo(wxWindow *parent)
120117
AddCtrl(_("Video resolution:"), resolution_shortcuts(&d, width, height));
121118
AddCtrl("", res_sizer);
122119
AddCtrl(_("Color:"), color_sizer);
123-
AddCtrl(_("Frame rate (fps):"), spin_ctrl(&d, .1, 1000.0, &fps));
120+
wxTextValidator fpsVal(wxFILTER_INCLUDE_CHAR_LIST, &fps);
121+
fpsVal.SetCharIncludes("0123456789./");
122+
AddCtrl(_("Frame rate (fps):"), new wxTextCtrl(&d, -1, "", wxDefaultPosition, wxDefaultSize, 0, fpsVal));
124123
AddCtrl(_("Duration (frames):"), spin_ctrl(&d, 2, 36000000, &length)); // Ten hours of 1k FPS
125124
AddCtrl("", length_display = new wxStaticText(&d, -1, ""));
126125

@@ -132,17 +131,19 @@ DialogDummyVideo::DialogDummyVideo(wxWindow *parent)
132131
main_sizer->Add(new wxStaticLine(&d, wxHORIZONTAL), wxSizerFlags().HorzBorder().Expand());
133132
main_sizer->Add(btn_sizer, wxSizerFlags().Expand().Border());
134133

135-
UpdateLengthDisplay();
134+
btn_sizer->GetAffirmativeButton()->Enable(UpdateLengthDisplay());
136135

137136
d.SetSizerAndFit(main_sizer);
138137
d.CenterOnParent();
139138

140139
d.Bind(wxEVT_COMBOBOX, &DialogDummyVideo::OnResolutionShortcut, this);
141140
color_btn->Bind(EVT_COLOR, [=, this](ValueEvent<agi::Color>& e) { color = e.Get(); });
142-
d.Bind(wxEVT_SPINCTRL, [&](wxCommandEvent&) {
141+
auto on_update = [&, btn_sizer](wxCommandEvent&) {
143142
d.TransferDataFromWindow();
144-
UpdateLengthDisplay();
145-
});
143+
btn_sizer->GetAffirmativeButton()->Enable(UpdateLengthDisplay());
144+
};
145+
d.Bind(wxEVT_SPINCTRL, on_update);
146+
d.Bind(wxEVT_TEXT, on_update);
146147
}
147148

148149
static void add_label(wxWindow *parent, wxSizer *sizer, wxString const& label) {
@@ -166,8 +167,16 @@ void DialogDummyVideo::OnResolutionShortcut(wxCommandEvent &e) {
166167
d.TransferDataToWindow();
167168
}
168169

169-
void DialogDummyVideo::UpdateLengthDisplay() {
170-
length_display->SetLabel(fmt_tl("Resulting duration: %s", agi::Time(length / fps * 1000).GetAssFormatted(true)));
170+
bool DialogDummyVideo::UpdateLengthDisplay() {
171+
std::string dur = "-";
172+
bool valid = false;
173+
auto fr = DummyVideoProvider::TryParseFramerate(from_wx(fps));
174+
if (fr.has_value()) {
175+
dur = agi::Time(fr.value().TimeAtFrame(length)).GetAssFormatted(true);
176+
valid = true;
177+
}
178+
length_display->SetLabel(fmt_tl("Resulting duration: %s", dur));
179+
return valid;
171180
}
172181
}
173182

@@ -176,12 +185,12 @@ std::string CreateDummyVideo(wxWindow *parent) {
176185
if (dlg.d.ShowModal() != wxID_OK)
177186
return "";
178187

179-
OPT_SET("Video/Dummy/FPS")->SetDouble(dlg.fps);
188+
OPT_SET("Video/Dummy/FPS String")->SetString(from_wx(dlg.fps));
180189
OPT_SET("Video/Dummy/Last/Width")->SetInt(dlg.width);
181190
OPT_SET("Video/Dummy/Last/Height")->SetInt(dlg.height);
182191
OPT_SET("Video/Dummy/Last/Length")->SetInt(dlg.length);
183192
OPT_SET("Colour/Video Dummy/Last Colour")->SetColor(dlg.color);
184193
OPT_SET("Video/Dummy/Pattern")->SetBool(dlg.pattern);
185194

186-
return DummyVideoProvider::MakeFilename(dlg.fps, dlg.length, dlg.width, dlg.height, dlg.color, dlg.pattern);
195+
return DummyVideoProvider::MakeFilename(from_wx(dlg.fps), dlg.length, dlg.width, dlg.height, dlg.color, dlg.pattern);
187196
}

src/libresrc/default_config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@
606606
"Maximized" : false
607607
},
608608
"Dummy" : {
609-
"FPS" : 23.975999999999999091,
609+
"FPS String" : "24000/1001",
610610
"Last" : {
611611
"Height" : 720,
612612
"Length" : 40000,

src/video_provider_dummy.cpp

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@
3838
#include "video_frame.h"
3939

4040
#include <libaegisub/color.h>
41+
#include <libaegisub/exception.h>
4142
#include <libaegisub/split.h>
4243
#include <libaegisub/util.h>
4344

4445
#include <boost/algorithm/string/predicate.hpp>
4546
#include <libaegisub/format.h>
4647
#include <boost/gil.hpp>
4748

48-
DummyVideoProvider::DummyVideoProvider(double fps, int frames, int width, int height, agi::Color colour, bool pattern)
49+
DummyVideoProvider::DummyVideoProvider(agi::vfr::Framerate fps, int frames, int width, int height, agi::Color colour, bool pattern)
4950
: framecount(frames)
5051
, fps(fps)
5152
, width(width)
@@ -85,8 +86,36 @@ DummyVideoProvider::DummyVideoProvider(double fps, int frames, int width, int he
8586
}
8687
}
8788

88-
std::string DummyVideoProvider::MakeFilename(double fps, int frames, int width, int height, agi::Color colour, bool pattern) {
89-
return agi::format("?dummy:%f:%d:%d:%d:%d:%d:%d:%s", fps, frames, width, height, (int)colour.r, (int)colour.g, (int)colour.b, (pattern ? "c" : ""));
89+
std::optional<agi::vfr::Framerate> DummyVideoProvider::TryParseFramerate(std::string fps_string) {
90+
using agi::util::try_parse;
91+
92+
double fps_double;
93+
if (try_parse(fps_string, &fps_double)) {
94+
try {
95+
return agi::vfr::Framerate(fps_double);
96+
} catch (agi::vfr::InvalidFramerate) {
97+
return {};
98+
}
99+
} else {
100+
std::vector<std::string> numden;
101+
agi::Split(numden, fps_string, '/');
102+
if (numden.size() != 2)
103+
return {};
104+
105+
int num, den;
106+
if (!try_parse(numden[0], &num)) return {};
107+
if (!try_parse(numden[1], &den)) return {};
108+
109+
try {
110+
return agi::vfr::Framerate(num, den);
111+
} catch (agi::vfr::InvalidFramerate) {
112+
return {};
113+
}
114+
}
115+
}
116+
117+
std::string DummyVideoProvider::MakeFilename(std::string fps, int frames, int width, int height, agi::Color colour, bool pattern) {
118+
return agi::format("?dummy:%s:%d:%d:%d:%d:%d:%d:%s", fps, frames, width, height, (int)colour.r, (int)colour.g, (int)colour.b, (pattern ? "c" : ""));
90119
}
91120

92121
void DummyVideoProvider::GetFrame(int, VideoFrame &frame) {
@@ -99,21 +128,22 @@ void DummyVideoProvider::GetFrame(int, VideoFrame &frame) {
99128

100129
namespace agi { class BackgroundRunner; }
101130
std::unique_ptr<VideoProvider> CreateDummyVideoProvider(agi::fs::path const& filename, std::string_view, agi::BackgroundRunner *) {
102-
if (!boost::starts_with(filename.string(), "?dummy"))
131+
// Use filename.generic_string here so forward slashes stay as they are
132+
if (!boost::starts_with(filename.generic_string(), "?dummy"))
103133
return {};
104134

105135
std::vector<std::string> toks;
106-
auto fields = filename.string().substr(7);
136+
auto fields = filename.generic_string().substr(7);
107137
agi::Split(toks, fields, ':');
108138
if (toks.size() != 8)
109139
throw VideoOpenError("Too few fields in dummy video parameter list");
110140

111141
size_t i = 0;
112-
double fps;
113142
int frames, width, height, red, green, blue;
114143

115144
using agi::util::try_parse;
116-
if (!try_parse(toks[i++], &fps)) throw VideoOpenError("Unable to parse fps field in dummy video parameter list");
145+
auto fps = DummyVideoProvider::TryParseFramerate(toks[i++]);
146+
if (!fps.has_value()) throw VideoOpenError("Unable to parse fps field in dummy video parameter list");
117147
if (!try_parse(toks[i++], &frames)) throw VideoOpenError("Unable to parse framecount field in dummy video parameter list");
118148
if (!try_parse(toks[i++], &width)) throw VideoOpenError("Unable to parse width field in dummy video parameter list");
119149
if (!try_parse(toks[i++], &height)) throw VideoOpenError("Unable to parse height field in dummy video parameter list");
@@ -123,5 +153,5 @@ std::unique_ptr<VideoProvider> CreateDummyVideoProvider(agi::fs::path const& fil
123153

124154
bool pattern = toks[i] == "c";
125155

126-
return std::make_unique<DummyVideoProvider>(fps, frames, width, height, agi::Color(red, green, blue), pattern);
156+
return std::make_unique<DummyVideoProvider>(fps.value(), frames, width, height, agi::Color(red, green, blue), pattern);
127157
}

src/video_provider_dummy.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434

3535
#include "include/aegisub/video_provider.h"
3636

37+
#include <optional>
38+
3739
namespace agi { struct Color; }
3840

3941
/// @class DummyVideoProvider
@@ -58,11 +60,12 @@ class DummyVideoProvider final : public VideoProvider {
5860
/// @param height Height in pixels of the dummy video
5961
/// @param colour Primary colour of the dummy video
6062
/// @param pattern Use a checkerboard pattern rather than a solid colour
61-
DummyVideoProvider(double fps, int frames, int width, int height, agi::Color colour, bool pattern);
63+
DummyVideoProvider(agi::vfr::Framerate fps, int frames, int width, int height, agi::Color colour, bool pattern);
6264

6365
/// Make a fake filename which when passed to the constructor taking a
6466
/// string will result in a video with the given parameters
65-
static std::string MakeFilename(double fps, int frames, int width, int height, agi::Color colour, bool pattern);
67+
static std::string MakeFilename(std::string fps, int frames, int width, int height, agi::Color colour, bool pattern);
68+
static std::optional<agi::vfr::Framerate> TryParseFramerate(std::string fps_string);
6669

6770
void GetFrame(int n, VideoFrame &frame) override;
6871
void SetColorSpace(std::string const&) override { }

0 commit comments

Comments
 (0)