#include "viewer.h" #include "ui_viewer.h" #include "playback/audio.h" #include "timeline.h" #include "project/sequence.h" #include "panels/panels.h" #include "io/config.h" #include "io/media.h" #include "project/undo.h" #include "ui/audiomonitor.h" #include "ui_timeline.h" #define FRAMES_IN_ONE_MINUTE 1798 // 1800 - 2 #define FRAMES_IN_TEN_MINUTES 17978 // (FRAMES_IN_ONE_MINUTE * 10) - 2 QString panel_name = "Viewer: "; extern "C" { #include #include } #include #include Viewer::Viewer(QWidget *parent) : QDockWidget(parent), ui(new Ui::Viewer), seq(NULL), playing(false), created_sequence(false) { ui->setupUi(this); ui->headers->viewer = this; ui->headers->snapping = false; ui->headers->show_text(false); ui->glViewerPane->child = ui->openGLWidget; ui->openGLWidget->viewer = this; viewer_widget = ui->openGLWidget; set_media(MEDIA_TYPE_SEQUENCE, NULL); ui->currentTimecode->setEnabled(false); ui->currentTimecode->set_minimum_value(0); ui->currentTimecode->set_default_value(qSNaN()); ui->currentTimecode->set_value(0, false); ui->currentTimecode->set_display_type(LABELSLIDER_FRAMENUMBER); connect(ui->currentTimecode, SIGNAL(valueChanged()), this, SLOT(update_playhead())); connect(&playback_updater, SIGNAL(timeout()), this, SLOT(timer_update())); update_playhead_timecode(0); update_end_timecode(); } Viewer::~Viewer() { delete ui; } bool Viewer::is_focused() { return ui->headers->hasFocus() || ui->openGLWidget->hasFocus() || ui->pushButton->hasFocus() || ui->pushButton_2->hasFocus() || ui->pushButton_3->hasFocus() || ui->pushButton_4->hasFocus() || ui->pushButton_5->hasFocus(); } void Viewer::set_main_sequence() { clean_created_seq(); set_sequence(true, sequence); } void Viewer::reset_all_audio() { // reset all clip audio if (seq != NULL) { audio_ibuffer_frame = seq->playhead; audio_ibuffer_timecode = (double) audio_ibuffer_frame / seq->frame_rate; for (int i=0;iclips.size();i++) { Clip* c = seq->clips.at(i); if (c != NULL) { c->reset_audio(); } } } clear_audio_ibuffer(); } QString frame_to_timecode(long f, int view, double frame_rate) { if (view == TIMECODE_FRAMES) { return QString::number(f); } // return timecode int hours = 0; int mins = 0; int secs = 0; int frames = 0; QString token = ":"; if (view == TIMECODE_DROP && frame_rate_is_droppable(frame_rate)) { //CONVERT A FRAME NUMBER TO DROP FRAME TIMECODE //Code by David Heidelberger, adapted from Andrew Duncan, further adapted for Olive by Olive Team //Given an int called framenumber and a double called framerate //Framerate should be 29.97, 59.94, or 23.976, otherwise the calculations will be off. int d; int m; int dropFrames = round(frame_rate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate int framesPerHour = round(frame_rate*60*60); //Number of frames in an hour int framesPer24Hours = framesPerHour*24; //Number of frames in a day - timecode rolls over after 24 hours int framesPer10Minutes = round(frame_rate * 60 * 10); //Number of frames per ten minutes int framesPerMinute = (round(frame_rate)*60)- dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames //If framenumber is greater than 24 hrs, next operation will rollover clock f = f % framesPer24Hours; //% is the modulus operator, which returns a remainder. a % b = the remainder of a/b d = f / framesPer10Minutes; // \ means integer division, which is a/b without a remainder. Some languages you could use floor(a/b) m = f % framesPer10Minutes; //In the original post, the next line read m>1, which only worked for 29.97. Jean-Baptiste Mardelle correctly pointed out that m should be compared to dropFrames. if (m > dropFrames) { f = f + (dropFrames*9*d) + dropFrames * ((m - dropFrames) / framesPerMinute); } else { f = f + dropFrames*9*d; } int frRound = round(frame_rate); frames = f % frRound; secs = (f / frRound) % 60; mins = ((f / frRound) / 60) % 60; hours = (((f / frRound) / 60) / 60); token = ";"; } else { // non-drop timecode int int_fps = qRound(frame_rate); hours = f/ (3600 * int_fps); mins = f / (60*int_fps) % 60; secs = f/int_fps % 60; frames = f%int_fps; } return QString(QString::number(hours).rightJustified(2, '0') + ":" + QString::number(mins).rightJustified(2, '0') + ":" + QString::number(secs).rightJustified(2, '0') + token + QString::number(frames).rightJustified(2, '0') ); } bool frame_rate_is_droppable(float rate) { return (qFuzzyCompare(rate, 23.976f) || qFuzzyCompare(rate, 29.97f) || qFuzzyCompare(rate, 59.94f)); } void Viewer::seek(long p) { pause(); seq->playhead = p; update_parents(); } void Viewer::go_to_start() { seek(0); } void Viewer::previous_frame() { if (seq->playhead > 0) seek(seq->playhead-1); } void Viewer::next_frame() { seek(seq->playhead+1); } void Viewer::toggle_play() { if (playing) { pause(); } else { play(); } } void Viewer::play() { if (audio_output->format().sampleRate() != seq->audio_frequency || audio_output->format().channelCount() != av_get_channel_layout_nb_channels(seq->audio_layout)) { init_audio(seq); } reset_all_audio(); playhead_start = seq->playhead; start_msecs = QDateTime::currentMSecsSinceEpoch(); playback_updater.start(); playing = true; set_playpause_icon(false); audio_thread->notifyReceiver(); } void Viewer::pause() { playing = false; set_playpause_icon(true); playback_updater.stop(); } void Viewer::go_to_end() { seek(seq->getEndFrame()); } void Viewer::update_playhead_timecode(long p) { ui->currentTimecode->set_value(p, false); } void Viewer::update_end_timecode() { ui->endTimecode->setText((seq == NULL) ? frame_to_timecode(0, config.timecode_view, 30) : frame_to_timecode(seq->getEndFrame(), config.timecode_view, seq->frame_rate)); } void Viewer::update_header_zoom() { if (seq != NULL) { long sequenceEndFrame = seq->getEndFrame(); ui->headers->update_zoom((sequenceEndFrame > 0) ? ((double) ui->headers->width() / (double) sequenceEndFrame) : 1); } } void Viewer::update_parents() { if (main_sequence) { update_ui(false); } else { update_viewer(); } } void Viewer::update_viewer() { viewer_widget->update(); update_header_zoom(); if (seq != NULL) update_playhead_timecode(seq->playhead); update_end_timecode(); } void Viewer::clear_inout_point() { if (seq->using_workarea) { undo_stack.push(new SetTimelineInOutCommand(seq, false, 0, 0)); update_parents(); } } void Viewer::set_in_point() { ui->headers->set_in_point(seq->playhead); } void Viewer::set_out_point() { ui->headers->set_out_point(seq->playhead); } void Viewer::set_media(int type, void* media) { main_sequence = false; clean_created_seq(); switch (type) { case MEDIA_TYPE_FOOTAGE: { Media* footage = static_cast(media); seq = new Sequence(); created_sequence = true; seq->wrapper_sequence = true; seq->name = footage->name; seq->using_workarea = footage->using_inout; seq->workarea_in = footage->in; seq->workarea_out = footage->out; if (footage->video_tracks.size() > 0) { MediaStream* video_stream = footage->video_tracks.at(0); seq->width = video_stream->video_width; seq->height = video_stream->video_height; if (video_stream->video_frame_rate > 0) seq->frame_rate = video_stream->video_frame_rate; Clip* c = new Clip(seq); c->media = footage; c->media_type = type; c->media_stream = video_stream->file_index; c->timeline_in = 0; c->timeline_out = footage->get_length_in_frames(seq->frame_rate); c->track = -1; c->clip_in = 0; c->recalculateMaxLength(); seq->clips.append(c); } else { seq->width = 1920; seq->height = 1080; seq->frame_rate = 30; } if (footage->audio_tracks.size() > 0) { MediaStream* audio_stream = footage->audio_tracks.at(0); seq->audio_frequency = audio_stream->audio_frequency; Clip* c = new Clip(seq); c->media = footage; c->media_type = type; c->media_stream = audio_stream->file_index; c->timeline_in = 0; c->timeline_out = footage->get_length_in_frames(seq->frame_rate); c->track = 0; c->clip_in = 0; c->recalculateMaxLength(); seq->clips.append(c); } else { seq->audio_frequency = 48000; } seq->audio_layout = AV_CH_LAYOUT_STEREO; set_sequence(false, seq); } break; case MEDIA_TYPE_SEQUENCE: set_sequence(false, static_cast(media)); break; } } void Viewer::on_pushButton_clicked() { go_to_start(); } void Viewer::on_pushButton_5_clicked() { go_to_end(); } void Viewer::on_pushButton_2_clicked() { previous_frame(); } void Viewer::on_pushButton_4_clicked() { next_frame(); } void Viewer::on_pushButton_3_clicked() { toggle_play(); } void Viewer::update_playhead() { seek(ui->currentTimecode->value()); } void Viewer::timer_update() { seq->playhead = round(playhead_start + ((QDateTime::currentMSecsSinceEpoch()-start_msecs) * 0.001 * seq->frame_rate)); update_parents(); } void Viewer::clean_created_seq() { if (created_sequence) { // TODO delete undo commands referencing this sequence to avoid crashes /*for (int i=0;iheaders->setEnabled(!null_sequence); ui->currentTimecode->setEnabled(!null_sequence); ui->openGLWidget->setEnabled(!null_sequence); ui->openGLWidget->setVisible(!null_sequence); ui->pushButton->setEnabled(!null_sequence); ui->pushButton_2->setEnabled(!null_sequence); ui->pushButton_3->setEnabled(!null_sequence); ui->pushButton_4->setEnabled(!null_sequence); ui->pushButton_5->setEnabled(!null_sequence); init_audio(seq); if (!null_sequence) { ui->currentTimecode->set_frame_rate(seq->frame_rate); playback_updater.setInterval(qFloor(1000 / seq->frame_rate)); update_playhead_timecode(seq->playhead); update_end_timecode(); ui->glViewerPane->aspect_ratio = (float) seq->width / (float) seq->height; ui->glViewerPane->adjust(); setWindowTitle(panel_name + seq->name); } else { update_playhead_timecode(0); update_end_timecode(); setWindowTitle(panel_name + "(none)"); } update_header_zoom(); viewer_widget->update(); update(); } void Viewer::set_playpause_icon(bool play) { ui->pushButton_3->setIcon(QIcon((play) ? ":/icons/play.png" : ":/icons/pause.png")); }