]> git.sesse.net Git - nageru/blobdiff - futatabi/mainwindow.cpp
Add a multitrack export action.
[nageru] / futatabi / mainwindow.cpp
index 2a05a24428a3774c76bfb04b67079e85e0c9f4d6..6d0f7c9767bf8e3f0f2d47716f97a6b0ed2b5b3f 100644 (file)
@@ -1,6 +1,8 @@
 #include "mainwindow.h"
 
+#include "shared/aboutdialog.h"
 #include "clip_list.h"
+#include "export.h"
 #include "shared/disk_space_estimator.h"
 #include "flags.h"
 #include "frame_on_disk.h"
@@ -9,6 +11,9 @@
 #include "shared/timebase.h"
 #include "ui_mainwindow.h"
 
+#include <QDesktopServices>
+#include <QFileDialog>
+#include <QMessageBox>
 #include <QMouseEvent>
 #include <QShortcut>
 #include <QTimer>
@@ -36,6 +41,9 @@ MainWindow::MainWindow()
 
        // 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);
+       connect(ui->manual_action, &QAction::triggered, this, &MainWindow::manual_triggered);
+       connect(ui->about_action, &QAction::triggered, this, &MainWindow::about_triggered);
 
        global_disk_space_estimator = new DiskSpaceEstimator(bind(&MainWindow::report_disk_space, this, _1, _2));
        disk_free_label = new QLabel(this);
@@ -121,8 +129,8 @@ MainWindow::MainWindow()
                this, &MainWindow::playlist_selection_changed);
        playlist_selection_changed();  // First time set-up.
 
-       preview_player = new Player(ui->preview_display, /*also_output_to_stream=*/false);
-       live_player = new Player(ui->live_display, /*also_output_to_stream=*/true);
+       preview_player.reset(new Player(ui->preview_display, /*also_output_to_stream=*/false));
+       live_player.reset(new Player(ui->live_display, /*also_output_to_stream=*/true));
        live_player->set_done_callback([this]{
                post_to_main_thread([this]{
                        live_player_clip_done();
@@ -144,6 +152,11 @@ MainWindow::MainWindow()
                this, &MainWindow::clip_list_selection_changed);
 }
 
+MainWindow::~MainWindow()
+{
+       // Empty so that we can forward-declare Player in the .h file.
+}
+
 void MainWindow::cue_in_clicked()
 {
        if (!cliplist_clips->empty() && cliplist_clips->back()->pts_out < 0) {
@@ -154,6 +167,7 @@ void MainWindow::cue_in_clicked()
        clip.pts_in = current_pts;
        cliplist_clips->add_clip(clip);
        playlist_selection_changed();
+       ui->clip_list->scrollToBottom();
 }
 
 void MainWindow::cue_out_clicked()
@@ -177,6 +191,7 @@ void MainWindow::queue_clicked()
                if (clip.pts_out != -1) {
                        playlist_clips->add_clip(clip);
                        playlist_selection_changed();
+                       ui->playlist->scrollToBottom();
                }
                return;
        }
@@ -193,6 +208,12 @@ void MainWindow::queue_clicked()
        if (clip.pts_out != -1) {
                playlist_clips->add_clip(clip);
                playlist_selection_changed();
+               ui->playlist->scrollToBottom();
+               if (!ui->playlist->selectionModel()->hasSelection()) {
+                       // TODO: Figure out why this doesn't always seem to actually select the row.
+                       QModelIndex bottom = playlist_clips->index(playlist_clips->size() - 1, 0);
+                       ui->playlist->setCurrentIndex(bottom);
+               }
        }
 }
 
@@ -361,6 +382,9 @@ pair<Clip, size_t> MainWindow::live_player_get_next_clip()
 {
        // playlist_clips can only be accessed on the main thread.
        // Hopefully, we won't have to wait too long for this to come back.
+       //
+       // TODO: If MainWindow is in the process of being destroyed and waiting
+       // for Player to shut down, we could have a deadlock here.
        promise<pair<Clip, size_t>> clip_promise;
        future<pair<Clip, size_t>> clip = clip_promise.get_future();
        post_to_main_thread([this, &clip_promise] {
@@ -640,8 +664,7 @@ void MainWindow::preview_single_frame(int64_t pts, unsigned stream_idx, MainWind
                lock_guard<mutex> lock(frame_mu);
                if (frames[stream_idx].empty())
                        return;
-               auto it = lower_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts,
-                       [](const FrameOnDisk &frame, int64_t pts) { return frame.pts < pts; });
+               auto it = find_last_frame_before(frames[stream_idx], pts);
                if (it != frames[stream_idx].end()) {
                        pts = it->pts;
                }
@@ -650,8 +673,7 @@ void MainWindow::preview_single_frame(int64_t pts, unsigned stream_idx, MainWind
                lock_guard<mutex> lock(frame_mu);
                if (frames[stream_idx].empty())
                        return;
-               auto it = upper_bound(frames[stream_idx].begin(), frames[stream_idx].end(), pts - 1,
-                       [](int64_t pts, const FrameOnDisk &frame) { return pts < frame.pts; });
+               auto it = find_first_frame_at_or_after(frames[stream_idx], pts);
                if (it != frames[stream_idx].end()) {
                        pts = it->pts;
                }
@@ -735,6 +757,44 @@ void MainWindow::exit_triggered()
        close();
 }
 
+void MainWindow::export_cliplist_clip_multitrack_triggered()
+{
+       QItemSelectionModel *selected = ui->clip_list->selectionModel();
+       if (!selected->hasSelection()) {
+               QMessageBox msgbox;
+               msgbox.setText("No clip selected in the clip list. Select one and try exporting again.");
+               msgbox.exec();
+               return;
+       }
+
+       QModelIndex index = selected->currentIndex();
+       Clip clip = *cliplist_clips->clip(index.row());
+       QString filename = QFileDialog::getSaveFileName(this,
+               "Export multitrack clip", QString(), tr("Matroska video files (*.mkv)"));
+       if (filename.isNull()) {
+               // Cancel.
+               return;
+       }
+       if (!filename.endsWith(".mkv")) {
+               filename += ".mkv";
+       }
+       export_multitrack_clip(filename.toStdString(), clip);
+}
+
+void MainWindow::manual_triggered()
+{
+       if (!QDesktopServices::openUrl(QUrl("https://nageru.sesse.net/doc/"))) {
+               QMessageBox msgbox;
+               msgbox.setText("Could not launch manual in web browser.\nPlease see https://nageru.sesse.net/doc/ manually.");
+               msgbox.exec();
+       }
+}
+
+void MainWindow::about_triggered()
+{
+       AboutDialog("Futatabi", "Multicamera slow motion video server").exec();
+}
+
 void MainWindow::highlight_camera_input(int stream_idx)
 {
        if (stream_idx == 0) {