diff --git a/effects/effect.cpp b/effects/effect.cpp index 051201866..77ef23dec 100644 --- a/effects/effect.cpp +++ b/effects/effect.cpp @@ -467,19 +467,23 @@ EffectRow::EffectRow(Effect *parent, QGridLayout *uilayout, const QString &n, in keyframe_enable->setCheckable(true); keyframe_enable->setToolTip("Enable Keyframes"); connect(keyframe_enable, SIGNAL(clicked(bool)), this, SLOT(set_keyframe_enabled(bool))); + connect(keyframe_enable, SIGNAL(toggled(bool)), this, SLOT(keyframe_ui_enabled(bool))); key_controls->addWidget(keyframe_enable); - QPushButton* left_key_nav = new QPushButton("<"); + left_key_nav = new QPushButton("<"); + left_key_nav->setVisible(false); left_key_nav->setMaximumSize(button_size); key_controls->addWidget(left_key_nav); connect(left_key_nav, SIGNAL(clicked(bool)), this, SLOT(goto_previous_key())); - QPushButton* key_addremove = new QPushButton("."); + key_addremove = new QPushButton("."); + key_addremove->setVisible(false); key_addremove->setMaximumSize(button_size); key_controls->addWidget(key_addremove); connect(key_addremove, SIGNAL(clicked(bool)), this, SLOT(toggle_key())); - QPushButton* right_key_nav = new QPushButton(">"); + right_key_nav = new QPushButton(">"); + right_key_nav->setVisible(false); right_key_nav->setMaximumSize(button_size); key_controls->addWidget(right_key_nav); connect(right_key_nav, SIGNAL(clicked(bool)), this, SLOT(goto_next_key())); @@ -515,6 +519,12 @@ void EffectRow::set_keyframe_enabled(bool enabled) { } } +void EffectRow::keyframe_ui_enabled(bool enabled) { + left_key_nav->setVisible(enabled); + key_addremove->setVisible(enabled); + right_key_nav->setVisible(enabled); +} + void EffectRow::goto_previous_key() { long key = LONG_MIN; Clip* c = parent_effect->parent_clip; diff --git a/effects/effect.h b/effects/effect.h index 20f19ae71..489fa859b 100644 --- a/effects/effect.h +++ b/effects/effect.h @@ -161,6 +161,7 @@ public: QVector keyframe_types; private slots: void set_keyframe_enabled(bool); + void keyframe_ui_enabled(bool); void goto_previous_key(); void toggle_key(); void goto_next_key(); @@ -172,6 +173,9 @@ private: QVector fields; QPushButton* keyframe_enable; + QPushButton* left_key_nav; + QPushButton* key_addremove; + QPushButton* right_key_nav; bool just_made_unsafe_keyframe; }; diff --git a/io/config.cpp b/io/config.cpp index 57ed45d8b..8d02efba8 100644 --- a/io/config.cpp +++ b/io/config.cpp @@ -9,7 +9,7 @@ Config config; Config::Config() : saved_layout(false), - show_track_lines(false), + show_track_lines(true), scroll_zooms(false), img_seq_formats("jpg|jpeg|bmp|tiff|tif|psd|png|tga|jp2|gif"), edit_tool_selects_links(false), @@ -24,9 +24,7 @@ Config::Config() custom_title_safe_ratio(1), enable_drag_files_to_timeline(false), autoscale_by_default(false) -{ - -} +{} void Config::load(QString path) { QFile f(path); diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp index 99e1e8d6c..443929f6f 100644 --- a/io/previewgenerator.cpp +++ b/io/previewgenerator.cpp @@ -60,6 +60,7 @@ void PreviewGenerator::parse_media() { } ms->video_width = fmt_ctx->streams[i]->codecpar->width; ms->video_height = fmt_ctx->streams[i]->codecpar->height; + ms->video_interlacing = VIDEO_PROGRESSIVE; // default value, we get the true value later in generate_waveform() if (append) media->video_tracks.append(ms); } else if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { ms->audio_channels = fmt_ctx->streams[i]->codecpar->channels; @@ -170,7 +171,13 @@ void PreviewGenerator::generate_waveform() { s->video_preview = QImage(data, dstW, dstH, linesize[0], QImage::Format_RGB888); - // delete [] data; + // is video interlaced? + if (temp_frame->interlaced_frame) { + s->video_interlacing = (temp_frame->top_field_first) ? VIDEO_TOP_FIELD_FIRST : VIDEO_BOTTOM_FIELD_FIRST; + s->video_frame_rate *= 2; + } else { + s->video_interlacing = VIDEO_PROGRESSIVE; + } s->preview_done = true; diff --git a/panels/timeline.cpp b/panels/timeline.cpp index 888bc9bb0..0389ebec7 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -293,10 +293,12 @@ bool Timeline::focused() { } void Timeline::repaint_timeline(bool changed) { - if (playing) { - sequence->playhead = round(playhead_start + ((QDateTime::currentMSecsSinceEpoch()-start_msecs) * 0.001 * sequence->frame_rate)); - } else if (changed) { + if (changed) { reset_all_audio(); + update_effect_controls(); + if (!playing) panel_viewer->viewer_widget->update(); + } else if (playing) { + sequence->playhead = round(playhead_start + ((QDateTime::currentMSecsSinceEpoch()-start_msecs) * 0.001 * sequence->frame_rate)); } ui->headers->update(); @@ -304,8 +306,6 @@ void Timeline::repaint_timeline(bool changed) { ui->audio_area->update(); panel_effect_controls->update_keyframes(); - update_effect_controls(); - if (sequence != NULL) { panel_timeline->ui->horizontalScrollBar->setMaximum(qMax(0, getScreenPointFromFrame(panel_timeline->zoom, sequence->getEndFrame()) + 100 - ui->editAreas->width())); @@ -318,6 +318,7 @@ void Timeline::repaint_timeline(bool changed) { if (zoomChanged) { zoomChanged = false; int target_scroll = getScreenPointFromFrame(zoom, sequence->playhead)-(ui->editAreas->width()>>1); + // TODO find a way to gradually move towards target_scroll instead of just setting it? ui->horizontalScrollBar->setValue(target_scroll); } diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 9a2727d6a..d14bfe867 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -344,13 +344,7 @@ void cache_video_worker(Clip* c, long playhead, ClipCache* cache) { i++; } }*/ - - if (!error) { - // setting the cache to written even if it hasn't reached_end prevents playback from - // signaling a seek/reset because it wasn't able to find the frame - cache->written = true; - cache->unread = true; - } + cache->unread = true; /* new AVFilter solution */ if (!c->reached_end) { @@ -392,6 +386,7 @@ void cache_video_worker(Clip* c, long playhead, ClipCache* cache) { break; } } else { + cache->written = true; i++; cache->write_count = i; } @@ -400,6 +395,13 @@ void cache_video_worker(Clip* c, long playhead, ClipCache* cache) { cache->write_count = i; + /*if (!error) { + // setting the cache to written even if it hasn't reached_end prevents playback from + // signaling a seek/reset because it wasn't able to find the frame + cache->written = true; + cache->unread = true; + }*/ + cache->mutex.unlock(); } @@ -415,26 +417,39 @@ void reset_cache(Clip* c, long target_frame) { // flush ffmpeg codecs avcodec_flush_buffers(c->codecCtx); + // flush filtergraph + /*while (av_buffersink_get_frame(c->buffersink_ctx, temp) >= 0) { + qDebug() << "flushed?"; + av_frame_unref(temp); + }*/ + double timebase = av_q2d(c->stream->time_base); if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - // seeks to nearest keyframe (target_frame represents internal clip frame) - av_seek_frame(c->formatCtx, ms->file_index, (int64_t) qFloor(clip_frame_to_seconds(c, target_frame) / timebase), AVSEEK_FLAG_BACKWARD); + if (target_frame > 0) { + AVFrame* temp = av_frame_alloc(); - // play up to the frame we actually want - long retrieved_frame = 0; - target_frame--; - AVFrame* temp = av_frame_alloc(); - do { - retrieve_next_frame(c, temp); - if (retrieved_frame == 0) { - if (target_frame != -1) retrieved_frame = floor(temp->pts * timebase * av_q2d(c->stream->avg_frame_rate)); - } else { - retrieved_frame++; - } - } while (retrieved_frame < target_frame); + // seeks to nearest keyframe (target_frame represents internal clip frame) + int64_t seek_ts = qRound(clip_frame_to_seconds(c, target_frame) / timebase); + av_seek_frame(c->formatCtx, ms->file_index, seek_ts - (av_q2d(av_inv_q(c->stream->time_base))), AVSEEK_FLAG_BACKWARD); - av_frame_free(&temp); + qDebug() << "seek ts:" << seek_ts; + + // play up to the frame we actually want + int ret; + do { + ret = retrieve_next_frame(c, temp); + if (ret < 0) { + qDebug() << "[WARNING] Seeking terminated prematurely"; + break; + } + qDebug() << "received ts:" << temp->pts; + } while (temp->pts + temp->pkt_duration + temp->pkt_duration < seek_ts); + av_frame_unref(temp); + av_frame_free(&temp); + } else { + av_seek_frame(c->formatCtx, ms->file_index, 0, AVSEEK_FLAG_BACKWARD); + } } else if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { // seek (target_frame represents timeline timecode in frames, not clip timecode) int64_t timestamp = qRound(playhead_to_seconds(c, target_frame) / timebase); // TODO qRound here might lead to clicking? or might fix it... who knows @@ -521,7 +536,7 @@ void open_clip_worker(Clip* clip) { // allocate filtergraph clip->filter_graph = avfilter_graph_alloc(); if (clip->filter_graph == NULL) { - qDebug() << "couldn't create filtergraph"; + qDebug() << "[ERROR] Could not create filtergraph"; } char filter_args[512]; @@ -544,6 +559,7 @@ void open_clip_worker(Clip* clip) { clip->cache_size = (ms->infinite_length) ? 1 : ceil(av_q2d(clip->stream->avg_frame_rate)/4); // cache is half a second in total // if (clip->skip_type == SKIP_TYPE_SEEK) clip->cache_size *= 2; + if (ms->video_interlacing != VIDEO_PROGRESSIVE) clip->cache_size *= 2; clip->cache_A.frames = new AVFrame* [clip->cache_size]; clip->cache_B.frames = new AVFrame* [clip->cache_size]; @@ -571,19 +587,17 @@ void open_clip_worker(Clip* clip) { qDebug() << "[ERROR] Could not set output pixel format"; } - bool interlaced = false; - if (interlaced) { - // TODO make better - AVFilterContext* yadif_filter; - avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", "mode=3", NULL, clip->filter_graph); - - clip->speed *= 2; - - avfilter_link(clip->buffersrc_ctx, 0, yadif_filter, 0); - avfilter_link(yadif_filter, 0, clip->buffersink_ctx, 0); - } else { + if (ms->video_interlacing == VIDEO_PROGRESSIVE) { avfilter_link(clip->buffersrc_ctx, 0, clip->buffersink_ctx, 0); - } + } else { + AVFilterContext* yadif_filter; + char yadif_args[100]; + snprintf(yadif_args, sizeof(yadif_args), "mode=3:parity=%d", ((ms->video_interlacing == VIDEO_TOP_FIELD_FIRST) ? 0 : 1)); // try mode 1 + avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", yadif_args, NULL, clip->filter_graph); + + avfilter_link(clip->buffersrc_ctx, 0, yadif_filter, 0); + avfilter_link(yadif_filter, 0, clip->buffersink_ctx, 0); + } avfilter_graph_config(clip->filter_graph, NULL); } else if (clip->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -765,7 +779,7 @@ void Cacher::run() { open_clip_worker(clip); - while (caching) { + while (caching) { clip->can_cache.wait(&clip->lock); if (!caching) { break; diff --git a/playback/playback.cpp b/playback/playback.cpp index fea7e49c5..22425eb10 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -117,7 +117,7 @@ bool get_clip_frame(Clip* c, long playhead) { double rate = c->getMediaFrameRate(); if (c->skip_type == SKIP_TYPE_DISCARD) rate *= c->speed; - long clip_time = refactor_frame_number(sequence_clip_time, c->sequence->frame_rate, rate); + long clip_time = qMax(0L, refactor_frame_number(sequence_clip_time, c->sequence->frame_rate, rate)); AVFrame* current_frame = NULL; bool no_frame = false; @@ -170,6 +170,8 @@ bool get_clip_frame(Clip* c, long playhead) { } } else { // this is technically bad, unless we just seeked + c->cache_A.write_count = 0; + c->cache_B.write_count = 0; c->cache_A.unread = false; c->cache_B.unread = false; cache_needs_reset = true; @@ -204,8 +206,6 @@ bool get_clip_frame(Clip* c, long playhead) { } } - qDebug() << "current_frame is NULL:" << (current_frame == NULL); - if (current_frame != NULL) { // set up opengl texture if (c->texture == NULL) { @@ -224,7 +224,7 @@ bool get_clip_frame(Clip* c, long playhead) { return true; } else { texture_failed = true; - if (!no_frame) qDebug() << "[ERROR] Failed to retrieve frame from cache (R:" << clip_time << "| A:" << c->cache_A.offset << "-" << c->cache_A.offset+c->cache_size-1 << "| B:" << c->cache_B.offset << "-" << c->cache_B.offset+c->cache_size-1 << "| WA:" << c->cache_A.written << "| WB:" << c->cache_B.written << ")"; + qDebug() << "[ERROR] Failed to retrieve frame from cache (R:" << clip_time << "| A:" << c->cache_A.offset << "-" << c->cache_A.offset+c->cache_size-1 << "| B:" << c->cache_B.offset << "-" << c->cache_B.offset+c->cache_size-1 << "| WA:" << c->cache_A.written << "| WB:" << c->cache_B.written << ")"; } } return false; @@ -242,7 +242,7 @@ double playhead_to_seconds(Clip* c, long playhead) { long seconds_to_clip_frame(Clip* c, double seconds) { // returns time as frame number (according to clip's frame rate) if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - return floor(seconds*av_q2d(c->stream->avg_frame_rate)); + return floor(seconds*c->getMediaFrameRate()); } else { qDebug() << "[ERROR] seconds_to_clip_frame only works on video streams"; return 0; @@ -251,7 +251,7 @@ long seconds_to_clip_frame(Clip* c, double seconds) { double clip_frame_to_seconds(Clip* c, long clip_frame) { // returns frame number in decimal seconds - return (double) clip_frame / av_q2d(c->stream->avg_frame_rate); + return (double) clip_frame / c->getMediaFrameRate(); } int retrieve_next_frame(Clip* c, AVFrame* f) { diff --git a/ui/timelineheader.cpp b/ui/timelineheader.cpp index 64a7b3bef..472c69d94 100644 --- a/ui/timelineheader.cpp +++ b/ui/timelineheader.cpp @@ -86,8 +86,7 @@ void TimelineHeader::set_out_point(long new_out) { } void TimelineHeader::mousePressEvent(QMouseEvent* event) { - qDebug() << "press???!?!?!??!"; - if (resizing_workarea) { + if (resizing_workarea) { sequence_end = sequence->getEndFrame(); } else { bool shift = (event->modifiers() & Qt::ShiftModifier); @@ -130,15 +129,11 @@ void TimelineHeader::mousePressEvent(QMouseEvent* event) { } set_playhead(event->pos().x()); } - } - qDebug() << "3"; - dragging = true; - qDebug() << "5" << dragging; - event->accept(); + } + dragging = true; } void TimelineHeader::mouseMoveEvent(QMouseEvent* event) { - qDebug() << "move"; if (dragging) { if (resizing_workarea) { long frame = getHeaderFrameFromScreenPoint(event->pos().x()); @@ -180,7 +175,7 @@ void TimelineHeader::mouseMoveEvent(QMouseEvent* event) { panel_timeline->repaint_timeline(false); } else { set_playhead(event->pos().x()); - } + } } else { resizing_workarea = false; unsetCursor(); @@ -200,13 +195,12 @@ void TimelineHeader::mouseMoveEvent(QMouseEvent* event) { setCursor(Qt::SizeHorCursor); } } - } - event->accept(); + } } void TimelineHeader::mouseReleaseEvent(QMouseEvent* event) { - qDebug() << "release???!?!?!??!"; - if (resizing_workarea) { + dragging = false; + if (resizing_workarea) { undo_stack.push(new SetTimelineInOutCommand(sequence, true, temp_workarea_in, temp_workarea_out)); } else if (dragging_markers && selected_markers.size() > 0) { bool moved = false; @@ -230,7 +224,6 @@ void TimelineHeader::mouseReleaseEvent(QMouseEvent* event) { dragging_markers = false; panel_timeline->snapped = false; panel_timeline->repaint_timeline(false); - event->accept(); } void TimelineHeader::focusOutEvent(QFocusEvent*) {