]> git.sesse.net Git - nageru/commitdiff
Persist quality settings in the database.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 15 Dec 2018 20:54:36 +0000 (21:54 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 15 Dec 2018 20:54:36 +0000 (21:54 +0100)
futatabi/db.cpp
futatabi/db.h
futatabi/flags.cpp
futatabi/flags.h
futatabi/mainwindow.cpp
futatabi/mainwindow.h
futatabi/mainwindow.ui
futatabi/state.proto
futatabi/video_stream.cpp

index 39fd55776c40c9f094e84bfbbb4aed906b65fed6..13e3f6ee7174dc105df1de4136dff33c905e5bd0 100644 (file)
@@ -18,6 +18,10 @@ DB::DB(const string &filename)
                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.
@@ -116,6 +120,83 @@ void DB::store_state(const StateProto &state)
        }
 }
 
+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;
index f8032c0e43d3502360fc74346de0ea87684a26f5..c0c3623975c7c10d1d67ccc36aa19a9bfc197484 100644 (file)
@@ -17,6 +17,9 @@ public:
        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;
index 7a8246030abf7d485836d467800612aaef5a7c95..1c606b13e53d62b97d556e2b8eb34f2dc8137bd8 100644 (file)
@@ -10,6 +10,7 @@
 using namespace std;
 
 Flags global_flags;
+int flow_initialized_interpolation_quality;
 
 // Long options that have no corresponding short option.
 enum LongOption {
@@ -82,6 +83,7 @@ void parse_flags(int argc, char * const argv[])
                        break;
                case 'q':
                        global_flags.interpolation_quality = atoi(optarg);
+                       global_flags.interpolation_quality_set = true;
                        break;
                case 'd':
                        global_flags.working_directory = optarg;
index 23be12ccf8f6ea239dfa9f7b4fe287d4cce05afb..a25b7e5438c81ed5c2d38da082820c82aa8f6b63 100644 (file)
@@ -10,12 +10,18 @@ struct Flags {
        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[]);
 
index 4e53590426cc38d21c348251e7ca3d7507a67f7d..852391051a287472a7eb9fd5a51d45e1dff55bbd 100644 (file)
@@ -50,6 +50,22 @@ MainWindow::MainWindow()
        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);
@@ -61,6 +77,32 @@ MainWindow::MainWindow()
        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;}");
@@ -374,6 +416,13 @@ void MainWindow::state_changed(const StateProto &state)
        }
 }
 
+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())
@@ -903,6 +952,25 @@ void MainWindow::redo_triggered()
        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) {
index 36126744eba8b370e91cb70557a2448052c70bba..0fa337f61e6d56dc5f5212277413848acd273ca6 100644 (file)
@@ -92,6 +92,7 @@ private:
        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);
@@ -112,6 +113,7 @@ private:
        void about_triggered();
        void undo_triggered();
        void redo_triggered();
+       void quality_toggled(int quality, bool checked);
 
        void highlight_camera_input(int stream_idx);
 
index 388e7d82aaa115cc120077ceb1cab653a0f6ac16..733ab217bec54102886d605ca22f337afe457f30 100644 (file)
    </property>
    <widget class="QMenu" name="menuFile">
     <property name="title">
-     <string>&amp;File</string>
+     <string>&amp;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 &amp;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 (&amp;0)</string>
+   </property>
+  </action>
+  <action name="quality_1_action">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>Fastest (&amp;1)</string>
+   </property>
+  </action>
+  <action name="quality_2_action">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>Default (&amp;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 (&amp;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 (&amp;4) (not realtime on any current GPU)</string>
+   </property>
+  </action>
  </widget>
  <customwidgets>
   <customwidget>
index d76bf02c6c66472a4f4d0b32727af1b46b98a00a..71e8cd6622e3d1d7628d2fbe2d5fb523fac6ff5f 100644 (file)
@@ -17,3 +17,7 @@ message StateProto {
        ClipListProto clip_list = 1;
        ClipListProto play_list = 2;
 }
+
+message SettingsProto {
+       int32 interpolation_quality = 1;  // 0 = unset, 1 = quality 0, 2 = quality 1, etc.
+}
index 8593936800a81307dc79e482b657a91cead7421b..30d4971dc9ac9fced0d7328ee3c877de48bf2c2b 100644 (file)
@@ -208,18 +208,16 @@ VideoStream::VideoStream(AVFormatContext *file_avctx)
        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);
        }