Files
oak-editor/app/widget/viewer/viewer.h
T

401 lines
9.4 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_VIEWER_WIDGET_H
#define OAK_VIEWER_WIDGET_H
#include <QFile>
#include <QLabel>
#include <QPushButton>
#include <QScrollBar>
#include <QTimer>
#include <QWidget>
#include "audio/audioprocessor.h"
#include "audiowaveformview.h"
#include "node/output/viewer/viewer.h"
#include "oakengine/playback.h"
#include "render/previewautocacher.h"
#include "viewerdisplay.h"
#include "viewersizer.h"
#include "viewerwindow.h"
#include "widget/playbackcontrols/playbackcontrols.h"
#include "widget/timebased/timebasedwidget.h"
#include "widget/timelinewidget/timelinewidget.h"
namespace olive
{
class MulticamWidget;
/**
* @brief An OpenGL-based viewer widget with playback controls (a PlaybackControls widget).
*/
class ViewerWidget : public TimeBasedWidget {
Q_OBJECT
public:
enum WaveformMode {
k_wf_automatic,
k_wf_viewer_only,
k_wf_waveform_only,
k_wf_viewer_and_waveform
};
ViewerWidget(QWidget *parent = nullptr)
: ViewerWidget(new ViewerDisplayWidget(), parent)
{
}
virtual ~ViewerWidget() override;
void set_playback_controls_enabled(bool enabled);
void set_time_ruler_enabled(bool enabled);
void toggle_play_pause();
bool is_playing() const;
/**
* @brief Enable or disable the color management menu
*
* While the Viewer is _always_ color managed, In some contexts, the color management may be controlled from an
* external UI making the menu unnecessary.
*/
void set_color_menu_enabled(bool enabled);
void set_matrix(const QMatrix4x4 &mat);
/**
* @brief Creates a ViewerWindow widget and places it full screen on another screen
*
* If `screen` is nullptr, the screen will be automatically selected as whichever one contains the mouse cursor.
*/
void set_full_screen(QScreen *screen = nullptr);
ColorManager *color_manager() const
{
return display_widget_->color_manager();
}
void set_gizmos(Node *node);
void start_capture(TimelineWidget *source, const TimeRange &time,
const Track::Reference &track);
void set_audio_scrubbing_enabled(bool e)
{
enable_audio_scrubbing_ = e;
}
void add_playback_device(ViewerDisplayWidget *vw)
{
playback_devices_.push_back(vw);
}
void set_timeline_selected_blocks(const QVector<Block *> &b)
{
timeline_selected_blocks_ = b;
if (!is_playing()) {
// If is playing, this will happen by the next frame automatically
detect_multicam_node_now();
update_texture_from_node();
}
}
void set_node_view_selections(const QVector<Node *> &n)
{
node_view_selected_ = n;
if (!is_playing()) {
// If is playing, this will happen by the next frame automatically
detect_multicam_node_now();
update_texture_from_node();
}
}
void connect_multicam_widget(MulticamWidget *p);
public slots:
void play(bool in_to_out_only);
void play();
void pause();
void shuttle_left();
void shuttle_stop();
void shuttle_right();
void set_color_transform(const ColorTransform &transform);
/**
* @brief Wrapper for ViewerGLWidget::SetSignalCursorColorEnabled()
*/
void set_signal_cursor_color_enabled(bool e);
void cache_entire_sequence();
void cache_sequence_in_out();
void set_viewer_resolution(int width, int height);
void set_viewer_pixel_aspect(const Rational &ratio);
void update_texture_from_node();
void request_start_editing_text()
{
display_widget_->request_start_editing_text();
}
signals:
/**
* @brief Wrapper for ViewerGLWidget::CursorColor()
*/
void cursor_color(const Color &reference, const Color &display);
/**
* @brief Signal emitted when a new frame is loaded
*/
void texture_changed(TexturePtr t);
/**
* @brief Wrapper for ViewerGLWidget::ColorProcessorChanged()
*/
void color_processor_changed(ColorProcessorPtr processor);
/**
* @brief Wrapper for ViewerGLWidget::ColorManagerChanged()
*/
void color_manager_changed(ColorManager *color_manager);
protected:
ViewerWidget(ViewerDisplayWidget *display, QWidget *parent = nullptr);
virtual void TimebaseChangedEvent(const Rational &) override;
virtual void TimeChangedEvent(const Rational &time) override;
virtual void ConnectNodeEvent(ViewerOutput *) override;
virtual void DisconnectNodeEvent(ViewerOutput *) override;
virtual void ConnectedNodeChangeEvent(ViewerOutput *) override;
virtual void ConnectedWorkAreaChangeEvent(TimelineWorkArea *) override;
virtual void ConnectedMarkersChangeEvent(TimelineMarkerList *) override;
virtual void ScaleChangedEvent(const double &s) override;
virtual void resizeEvent(QResizeEvent *event) override;
PlaybackControls *controls_;
ViewerDisplayWidget *display_widget() const
{
return display_widget_;
}
void IgnoreNextScrubEvent()
{
ignore_scrub_++;
}
RenderTicketPtr get_single_frame(const Rational &t, bool dry = false);
void set_waveform_mode(WaveformMode wf);
private:
int64_t get_timestamp() const
{
return Timecode::time_to_timestamp(get_connected_node()->get_playhead(),
timebase(), Timecode::k_floor);
}
void update_time_internal(int64_t i);
void play_internal(int speed, bool in_to_out_only);
void pause_internal();
void push_scrubbed_audio();
void update_minimum_scale();
void set_color_transform(const ColorTransform &transform,
ViewerDisplayWidget *sender);
QString get_cached_filename_from_time(const Rational &time);
bool frame_exists_at_time(const Rational &time);
bool viewer_might_be_a_still();
void set_display_image(RenderTicketPtr ticket);
RenderTicketPtr get_frame(const Rational &t);
static FramePtr decode_cached_image(const QString &cache_path,
const QUuid &cache_id,
const int64_t &time);
static void decode_cached_image(RenderTicketPtr ticket,
const QString &cache_path,
const QUuid &cache_id, const int64_t &time);
bool should_force_waveform() const;
void set_empty_image();
void update_auto_cacher();
void arm_for_recording();
void disarm_recording();
void close_audio_processor();
void detect_multicam_node(const Rational &time);
bool is_video_visible() const;
void deliver_facade_frame(const FramePtr &frame, const Rational &ts);
// Trampolines for the facade playback engine's pull-thread
// callbacks; both marshal onto the main thread.
static void facade_frame_callback(const oak_playback_frame *frame,
void *userdata);
static void facade_audio_callback(const oak_playback_audio *audio,
void *userdata);
ViewerSizer *sizer_;
int playback_speed_;
Rational last_time_;
bool color_menu_enabled_;
bool time_changed_from_timer_;
bool play_in_to_out_only_;
AudioWaveformView *waveform_view_;
QHash<QScreen *, ViewerWindow *> windows_;
ViewerDisplayWidget *display_widget_;
ViewerDisplayWidget *context_menu_widget_;
// Facade playback session (created on first play; the engine drives
// frames/audio from its pull thread and we poll its position).
OakEnginePlayback *playback_;
QTimer playback_poll_timer_;
// Sample-rate/channel converter for the audio SCRUB path (continuous
// playback audio is handled by the facade engine itself).
AudioProcessor audio_processor_;
QVector<ViewerDisplayWidget *> playback_devices_;
QList<RenderTicketWatcher *> nonqueue_watchers_;
Rational last_length_;
static QVector<ViewerWidget *> instances;
std::list<RenderTicketWatcher *> audio_scrub_watchers_;
bool record_armed_;
bool recording_;
TimelineWidget *recording_callback_;
TimeRange recording_range_;
Track::Reference recording_track_;
QString recording_filename_;
bool enable_audio_scrubbing_;
WaveformMode waveform_mode_;
int ignore_scrub_;
QVector<Block *> timeline_selected_blocks_;
QVector<Node *> node_view_selected_;
MulticamWidget *multicam_panel_;
private slots:
void playback_poll_update();
void length_changed_slot(const Rational &length);
void interlacing_changed_slot(VideoParams::Interlacing interlacing);
void update_renderer_video_parameters();
void update_renderer_audio_parameters();
void show_context_menu(const QPoint &pos);
void set_zoom_from_menu(QAction *action);
void update_waveform_view_from_mode();
void context_menu_set_full_screen(QAction *action);
void context_menu_set_playback_res(QAction *action);
void context_menu_disable_safe_margins();
void context_menu_set_safe_margins();
void context_menu_set_custom_safe_margins();
void window_about_to_close();
void renderer_generated_frame();
void viewer_invalidated_video_range(const olive::TimeRange &range);
void update_waveform_mode_from_menu(QAction *a);
void drag_entered(QDragEnterEvent *event);
void dropped(QDropEvent *event);
void received_audio_buffer_for_scrubbing();
void update_audio_processor();
void create_addable_at(const QRectF &f);
void show_subtitle_properties();
void save_frame_as_image();
void detect_multicam_node_now();
};
}
#endif // OAK_VIEWER_WIDGET_H