CREATE TABLE IF NOT EXISTS state (state BLOB);
)", nullptr, nullptr, nullptr); // Ignore errors.
+ sqlite3_exec(db, R"(
+ CREATE TABLE IF NOT EXISTS settings (settings BLOB);
+ )", nullptr, nullptr, nullptr); // Ignore errors.
+
sqlite3_exec(db, R"(
DROP TABLE file;
)", nullptr, nullptr, nullptr); // Ignore errors.
}
}
+SettingsProto DB::get_settings()
+{
+ SettingsProto settings;
+
+ sqlite3_stmt *stmt;
+ int ret = sqlite3_prepare_v2(db, "SELECT settings FROM settings", -1, &stmt, 0);
+ if (ret != SQLITE_OK) {
+ fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+
+ ret = sqlite3_step(stmt);
+ if (ret == SQLITE_ROW) {
+ bool ok = settings.ParseFromArray(sqlite3_column_blob(stmt, 0), sqlite3_column_bytes(stmt, 0));
+ if (!ok) {
+ fprintf(stderr, "State in database is corrupted!\n");
+ exit(1);
+ }
+ } else if (ret != SQLITE_DONE) {
+ fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+
+ ret = sqlite3_finalize(stmt);
+ if (ret != SQLITE_OK) {
+ fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+
+ return settings;
+}
+
+void DB::store_settings(const SettingsProto &settings)
+{
+ string serialized;
+ settings.SerializeToString(&serialized);
+
+ int ret = sqlite3_exec(db, "BEGIN", nullptr, nullptr, nullptr);
+ if (ret != SQLITE_OK) {
+ fprintf(stderr, "BEGIN: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+
+ ret = sqlite3_exec(db, "DELETE FROM settings", nullptr, nullptr, nullptr);
+ if (ret != SQLITE_OK) {
+ fprintf(stderr, "DELETE: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+
+ sqlite3_stmt *stmt;
+ ret = sqlite3_prepare_v2(db, "INSERT INTO settings VALUES (?)", -1, &stmt, 0);
+ if (ret != SQLITE_OK) {
+ fprintf(stderr, "INSERT prepare: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+
+ sqlite3_bind_blob(stmt, 1, serialized.data(), serialized.size(), SQLITE_STATIC);
+
+ ret = sqlite3_step(stmt);
+ if (ret == SQLITE_ROW) {
+ fprintf(stderr, "INSERT step: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+
+ ret = sqlite3_finalize(stmt);
+ if (ret != SQLITE_OK) {
+ fprintf(stderr, "INSERT finalize: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+
+ ret = sqlite3_exec(db, "COMMIT", nullptr, nullptr, nullptr);
+ if (ret != SQLITE_OK) {
+ fprintf(stderr, "COMMIT: %s\n", sqlite3_errmsg(db));
+ exit(1);
+ }
+}
+
vector<DB::FrameOnDiskAndStreamIdx> DB::load_frame_file(const string &filename, size_t size, unsigned filename_idx)
{
FileContentsProto file_contents;
StateProto get_state();
void store_state(const StateProto &state);
+ SettingsProto get_settings();
+ void store_settings(const SettingsProto &settings);
+
struct FrameOnDiskAndStreamIdx {
FrameOnDisk frame;
unsigned stream_idx;
using namespace std;
Flags global_flags;
+int flow_initialized_interpolation_quality;
// Long options that have no corresponding short option.
enum LongOption {
break;
case 'q':
global_flags.interpolation_quality = atoi(optarg);
+ global_flags.interpolation_quality_set = true;
break;
case 'd':
global_flags.working_directory = optarg;
std::string stream_source;
std::string working_directory = ".";
bool slow_down_input = false;
- int interpolation_quality = 2;
+ int interpolation_quality = 2; // Can be changed in the menus.
+ bool interpolation_quality_set = false;
uint16_t http_port = DEFAULT_HTTPD_PORT;
double output_framerate = 60000.0 / 1001.0;
};
extern Flags global_flags;
+// The quality setting that VideoStream was initialized to. The quality cannot
+// currently be changed, except turning interpolation completely off, so we compare
+// against this to give a warning.
+extern int flow_initialized_interpolation_quality;
+
void usage();
void parse_flags(int argc, char * const argv[]);
global_mainwindow = this;
ui->setupUi(this);
+ // Load settings from database if needed.
+ if (!global_flags.interpolation_quality_set) {
+ SettingsProto settings = db.get_settings();
+ if (settings.interpolation_quality() != 0) {
+ global_flags.interpolation_quality = settings.interpolation_quality() - 1;
+ }
+ }
+ if (global_flags.interpolation_quality == 0) {
+ // Allocate something just for simplicity; we won't be using it
+ // unless the user changes runtime, in which case 1 is fine.
+ flow_initialized_interpolation_quality = 1;
+ } else {
+ flow_initialized_interpolation_quality = global_flags.interpolation_quality;
+ }
+ save_settings();
+
// The menus.
connect(ui->exit_action, &QAction::triggered, this, &MainWindow::exit_triggered);
connect(ui->export_cliplist_clip_multitrack_action, &QAction::triggered, this, &MainWindow::export_cliplist_clip_multitrack_triggered);
ui->undo_action->setEnabled(false);
ui->redo_action->setEnabled(false);
+ // The quality group.
+ QActionGroup *quality_group = new QActionGroup(ui->interpolation_menu);
+ quality_group->addAction(ui->quality_0_action);
+ quality_group->addAction(ui->quality_1_action);
+ quality_group->addAction(ui->quality_2_action);
+ quality_group->addAction(ui->quality_3_action);
+ quality_group->addAction(ui->quality_4_action);
+ if (global_flags.interpolation_quality == 0) {
+ ui->quality_0_action->setChecked(true);
+ } else if (global_flags.interpolation_quality == 1) {
+ ui->quality_1_action->setChecked(true);
+ } else if (global_flags.interpolation_quality == 2) {
+ ui->quality_2_action->setChecked(true);
+ } else if (global_flags.interpolation_quality == 3) {
+ ui->quality_3_action->setChecked(true);
+ } else if (global_flags.interpolation_quality == 4) {
+ ui->quality_4_action->setChecked(true);
+ } else {
+ assert(false);
+ }
+ connect(ui->quality_0_action, &QAction::toggled, bind(&MainWindow::quality_toggled, this, 0, _1));
+ connect(ui->quality_1_action, &QAction::toggled, bind(&MainWindow::quality_toggled, this, 1, _1));
+ connect(ui->quality_2_action, &QAction::toggled, bind(&MainWindow::quality_toggled, this, 2, _1));
+ connect(ui->quality_3_action, &QAction::toggled, bind(&MainWindow::quality_toggled, this, 3, _1));
+ connect(ui->quality_4_action, &QAction::toggled, bind(&MainWindow::quality_toggled, this, 4, _1));
+
global_disk_space_estimator = new DiskSpaceEstimator(bind(&MainWindow::report_disk_space, this, _1, _2));
disk_free_label = new QLabel(this);
disk_free_label->setStyleSheet("QLabel {padding-right: 5px;}");
}
}
+void MainWindow::save_settings()
+{
+ SettingsProto settings;
+ settings.set_interpolation_quality(global_flags.interpolation_quality + 1);
+ db.store_settings(settings);
+}
+
void MainWindow::play_clicked()
{
if (playlist_clips->empty())
db.store_state(state);
}
+void MainWindow::quality_toggled(int quality, bool checked)
+{
+ if (!checked) {
+ return;
+ }
+ global_flags.interpolation_quality = quality;
+ if (quality != 0 && // Turning interpolation off is always possible.
+ quality != flow_initialized_interpolation_quality) {
+ QMessageBox msgbox;
+ msgbox.setText(QString::fromStdString(
+ "The interpolation quality for the main output cannot be changed at runtime, "
+ "except being turned completely off; it will take effect for exported files "
+ "only until next restart. The live output quality thus remains at " + to_string(flow_initialized_interpolation_quality) + "."));
+ msgbox.exec();
+ }
+
+ save_settings();
+}
+
void MainWindow::highlight_camera_input(int stream_idx)
{
if (stream_idx == 0) {
void defer_timer_expired();
void content_changed(); // In clip_list or play_list.
void state_changed(const StateProto &state); // Called post-filtering.
+ void save_settings();
enum Rounding { FIRST_AT_OR_AFTER, LAST_BEFORE };
void preview_single_frame(int64_t pts, unsigned stream_idx, Rounding rounding);
void about_triggered();
void undo_triggered();
void redo_triggered();
+ void quality_toggled(int quality, bool checked);
void highlight_camera_input(int stream_idx);
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
- <string>&File</string>
+ <string>&Video</string>
</property>
<widget class="QMenu" name="menu_Export">
<property name="title">
<addaction name="export_cliplist_clip_multitrack_action"/>
<addaction name="export_playlist_clip_interpolated_action"/>
</widget>
+ <widget class="QMenu" name="interpolation_menu">
+ <property name="title">
+ <string>Interpolation &quality</string>
+ </property>
+ <addaction name="quality_0_action"/>
+ <addaction name="quality_1_action"/>
+ <addaction name="quality_2_action"/>
+ <addaction name="quality_3_action"/>
+ <addaction name="quality_4_action"/>
+ </widget>
+ <addaction name="interpolation_menu"/>
<addaction name="menu_Export"/>
<addaction name="exit_action"/>
</widget>
<string>Ctrl+Y</string>
</property>
</action>
+ <action name="quality_0_action">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>No interpolation (&0)</string>
+ </property>
+ </action>
+ <action name="quality_1_action">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Fastest (&1)</string>
+ </property>
+ </action>
+ <action name="quality_2_action">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Default (&2) (realtime 720p on fast embedded GPUs)</string>
+ </property>
+ </action>
+ <action name="quality_3_action">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Good (&3) (realtime 720p on GTX 970 or so)</string>
+ </property>
+ </action>
+ <action name="quality_4_action">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Best (&4) (not realtime on any current GPU)</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
ClipListProto clip_list = 1;
ClipListProto play_list = 2;
}
+
+message SettingsProto {
+ int32 interpolation_quality = 1; // 0 = unset, 1 = quality 0, 2 = quality 1, etc.
+}
check_error();
OperatingPoint op;
- if (global_flags.interpolation_quality == 0) {
- // Allocate something just for simplicity; we won't be using it.
+ if (flow_initialized_interpolation_quality == 1) {
op = operating_point1;
- } else if (global_flags.interpolation_quality == 1) {
- op = operating_point1;
- } else if (global_flags.interpolation_quality == 2) {
+ } else if (flow_initialized_interpolation_quality == 2) {
op = operating_point2;
- } else if (global_flags.interpolation_quality == 3) {
+ } else if (flow_initialized_interpolation_quality == 3) {
op = operating_point3;
- } else if (global_flags.interpolation_quality == 4) {
+ } else if (flow_initialized_interpolation_quality == 4) {
op = operating_point4;
} else {
+ // Quality 0 will be changed to 1 in flags.cpp.
assert(false);
}