diff --git a/README.md b/README.md index 306c93ee9..4f3fee830 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,6 @@ Olive is a free non-linear video editor for Windows, macOS, and Linux. Discover more and download binaries at: https://www.olivevideoeditor.org/ -For compilation information, check the [wiki](https://github.com/olive-editor/olive/wiki). Currently the `master` branch serves more or less as the stable branch and the majority of development happens in the `alpha` branch. Before filing a feature request or bug report, please try the `alpha` branch first to ensure it hasn't already been addressed. +Compiling instructions for Windows, macOS, and Linux are [here](https://olivevideoeditor.org/compile.php). If you like Olive, please consider helping keep it alive by supporting it on [Patreon](https://www.patreon.com/olivevideoeditor). diff --git a/icons/dirup-disabled.png b/icons/dirup-disabled.png new file mode 100644 index 000000000..f23fa3f68 Binary files /dev/null and b/icons/dirup-disabled.png differ diff --git a/icons/dirup.png b/icons/dirup.png new file mode 100644 index 000000000..f67b44672 Binary files /dev/null and b/icons/dirup.png differ diff --git a/icons/icons.qrc b/icons/icons.qrc index 711fdcdc0..ac37d6339 100644 --- a/icons/icons.qrc +++ b/icons/icons.qrc @@ -49,5 +49,7 @@ transition-tool.png transition-tool-disabled.png error.png + dirup.png + dirup-disabled.png diff --git a/io/config.cpp b/io/config.cpp index bd0882f68..2ecb744ba 100644 --- a/io/config.cpp +++ b/io/config.cpp @@ -32,7 +32,8 @@ Config::Config() autoscroll(AUTOSCROLL_PAGE_SCROLL), audio_rate(48000), fast_seeking(false), - hover_focus(false) + hover_focus(false), + project_view_type(PROJECT_VIEW_TREE) {} void Config::load(QString path) { @@ -115,6 +116,9 @@ void Config::load(QString path) { } else if (stream.name() == "HoverFocus") { stream.readNext(); hover_focus = (stream.text() == "1"); + } else if (stream.name() == "ProjectViewType") { + stream.readNext(); + project_view_type = stream.text().toInt(); } } } @@ -163,6 +167,7 @@ void Config::save(QString path) { stream.writeTextElement("AudioRate", QString::number(audio_rate)); stream.writeTextElement("FastSeeking", QString::number(fast_seeking)); stream.writeTextElement("HoverFocus", QString::number(hover_focus)); + stream.writeTextElement("ProjectViewType", QString::number(project_view_type)); stream.writeEndElement(); // configuration stream.writeEndDocument(); // doc diff --git a/io/config.h b/io/config.h index 4092204ff..4972c9dd3 100644 --- a/io/config.h +++ b/io/config.h @@ -18,6 +18,9 @@ #define AUTOSCROLL_PAGE_SCROLL 1 #define AUTOSCROLL_SMOOTH_SCROLL 2 +#define PROJECT_VIEW_TREE 0 +#define PROJECT_VIEW_ICON 1 + struct Config { Config(); bool saved_layout; @@ -44,6 +47,7 @@ struct Config { int audio_rate; bool fast_seeking; bool hover_focus; + int project_view_type; void load(QString path); void save(QString path); diff --git a/io/exportthread.cpp b/io/exportthread.cpp index 52e6e6999..5715ce9f0 100644 --- a/io/exportthread.cpp +++ b/io/exportthread.cpp @@ -97,8 +97,8 @@ bool ExportThread::setupVideo() { } // allocate context - vcodec_ctx = video_stream->codec; -// vcodec_ctx = avcodec_alloc_context3(vcodec); +// vcodec_ctx = video_stream->codec; + vcodec_ctx = avcodec_alloc_context3(vcodec); if (!vcodec_ctx) { dout << "[ERROR] Could not allocate video encoding context"; ed->export_error = "could not allocate video encoding context"; @@ -125,7 +125,7 @@ bool ExportThread::setupVideo() { itoa(vcodec_ctx, buffer, 10);*/ //av_opt_set(vcodec_ctx->priv_data, "preset", "fast", AV_OPT_SEARCH_CHILDREN); - av_opt_set(vcodec_ctx->priv_data, "x264opts", "opencl", AV_OPT_SEARCH_CHILDREN); + //av_opt_set(vcodec_ctx->priv_data, "x264opts", "opencl", AV_OPT_SEARCH_CHILDREN); switch (video_compression_type) { case COMPRESSION_TYPE_CFR: @@ -203,8 +203,8 @@ bool ExportThread::setupAudio() { } // allocate context - acodec_ctx = audio_stream->codec; -// acodec_ctx = avcodec_alloc_context3(acodec); +// acodec_ctx = audio_stream->codec; + acodec_ctx = avcodec_alloc_context3(acodec); if (!acodec_ctx) { dout << "[ERROR] Could not find allocate audio encoding context"; ed->export_error = "could not allocate audio encoding context"; @@ -448,14 +448,14 @@ void ExportThread::run() { avcodec_close(vcodec_ctx); av_packet_unref(&video_pkt); av_frame_free(&video_frame); -// avcodec_free_context(&vcodec_ctx); + avcodec_free_context(&vcodec_ctx); } if (audio_enabled) { avcodec_close(acodec_ctx); av_packet_unref(&audio_pkt); av_frame_free(&audio_frame); -// avcodec_free_context(&acodec_ctx); + avcodec_free_context(&acodec_ctx); } avformat_free_context(fmt_ctx); diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp index 696c7987a..bb24f3cbd 100644 --- a/io/previewgenerator.cpp +++ b/io/previewgenerator.cpp @@ -128,6 +128,7 @@ bool PreviewGenerator::retrieve_preview(const QString& hash) { QFile f(thumb_path); if (f.exists() && ms->video_preview.load(thumb_path)) { //dout << "loaded thumb" << ms->file_index << "from" << thumb_path; + ms->make_square_thumb(); ms->preview_done = true; } else { found = false; @@ -255,7 +256,7 @@ void PreviewGenerator::generate_waveform() { if (!s->preview_done) { int dstH = 120; int dstW = dstH * ((float)temp_frame->width/(float)temp_frame->height); - uint8_t* data = new uint8_t[dstW*dstH*3]; + uint8_t* data = new uint8_t[dstW*dstH*4]; sws_ctx = sws_getContext( temp_frame->width, @@ -263,7 +264,7 @@ void PreviewGenerator::generate_waveform() { static_cast(temp_frame->format), dstW, dstH, - static_cast(AV_PIX_FMT_RGB24), + static_cast(AV_PIX_FMT_RGBA), SWS_FAST_BILINEAR, NULL, NULL, @@ -271,10 +272,11 @@ void PreviewGenerator::generate_waveform() { ); int linesize[AV_NUM_DATA_POINTERS]; - linesize[0] = dstW*3; + linesize[0] = dstW*4; sws_scale(sws_ctx, temp_frame->data, temp_frame->linesize, 0, temp_frame->height, &data, linesize); - s->video_preview = QImage(data, dstW, dstH, linesize[0], QImage::Format_RGB888); + s->video_preview = QImage(data, dstW, dstH, linesize[0], QImage::Format_RGBA8888); + s->make_square_thumb(); // is video interlaced? s->video_auto_interlacing = (temp_frame->interlaced_frame) ? ((temp_frame->top_field_first) ? VIDEO_TOP_FIELD_FIRST : VIDEO_BOTTOM_FIELD_FIRST) : VIDEO_PROGRESSIVE; diff --git a/mainwindow.cpp b/mainwindow.cpp index 404a6dc9b..adc24850e 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -130,15 +130,6 @@ MainWindow::MainWindow(QWidget *parent) : layout()->invalidate(); - // TODO maybe replace these with non-pointers later on? - panel_sequence_viewer = new Viewer(this); - panel_footage_viewer = new Viewer(this); - panel_project = new Project(this); - panel_effect_controls = new EffectControls(this); - panel_timeline = new Timeline(this); - - setup_layout(false); - connect(ui->menuWindow, SIGNAL(aboutToShow()), this, SLOT(windowMenu_About_To_Be_Shown())); connect(ui->menu_View, SIGNAL(aboutToShow()), this, SLOT(viewMenu_About_To_Be_Shown())); connect(ui->menu_Tools, SIGNAL(aboutToShow()), this, SLOT(toolMenu_About_To_Be_Shown())); @@ -215,6 +206,15 @@ MainWindow::MainWindow(QWidget *parent) : } } + // TODO maybe replace these with non-pointers later on? + panel_sequence_viewer = new Viewer(this); + panel_footage_viewer = new Viewer(this); + panel_project = new Project(this); + panel_effect_controls = new EffectControls(this); + panel_timeline = new Timeline(this); + + setup_layout(false); + init_audio(); connect(ui->action_Undo, SIGNAL(triggered(bool)), this, SLOT(undo())); @@ -439,7 +439,7 @@ void MainWindow::new_project() { panel_project->new_project(); updateTitle(""); update_ui(false); - panel_project->source_table->update(); + panel_project->tree_view->update(); } } diff --git a/mainwindow.ui b/mainwindow.ui index 61cc110c8..6533a2453 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -33,7 +33,7 @@ 0 0 653 - 25 + 16 @@ -148,6 +148,7 @@ + diff --git a/olive.pro b/olive.pro index f38efd430..f104d337e 100644 --- a/olive.pro +++ b/olive.pro @@ -1,207 +1,242 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2018-05-11T10:31:59 -# -#------------------------------------------------- - -QT += core gui multimedia opengl - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -TARGET = Olive -TEMPLATE = app - -# The following define makes your compiler emit warnings if you use -# any feature of Qt which has been marked as deprecated (the exact warnings -# depend on your compiler). Please consult the documentation of the -# deprecated API in order to know how to port your code away from it. -DEFINES += QT_DEPRECATED_WARNINGS - -# You can also make your code fail to compile if you use deprecated APIs. -# In order to do so, uncomment the following line. -# You can also select to disable deprecated APIs only up to a certain version of Qt. -#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - - -SOURCES += \ - main.cpp \ - mainwindow.cpp \ - panels/project.cpp \ - panels/effectcontrols.cpp \ - panels/viewer.cpp \ - panels/timeline.cpp \ - ui/sourcetable.cpp \ - dialogs/aboutdialog.cpp \ - ui/timelinewidget.cpp \ - project/media.cpp \ - project/footage.cpp \ - project/sequence.cpp \ - project/clip.cpp \ - playback/playback.cpp \ - playback/audio.cpp \ - io/config.cpp \ - dialogs/newsequencedialog.cpp \ - ui/viewerwidget.cpp \ - ui/viewercontainer.cpp \ - dialogs/exportdialog.cpp \ - ui/collapsiblewidget.cpp \ - panels/panels.cpp \ - playback/cacher.cpp \ - io/exportthread.cpp \ - ui/timelineheader.cpp \ - io/previewgenerator.cpp \ - ui/labelslider.cpp \ - dialogs/preferencesdialog.cpp \ - ui/audiomonitor.cpp \ - project/undo.cpp \ - ui/scrollarea.cpp \ - ui/comboboxex.cpp \ - ui/colorbutton.cpp \ - dialogs/replaceclipmediadialog.cpp \ - ui/fontcombobox.cpp \ - ui/checkboxex.cpp \ - ui/keyframeview.cpp \ - ui/texteditex.cpp \ - dialogs/demonotice.cpp \ - project/marker.cpp \ - dialogs/speeddialog.cpp \ - dialogs/mediapropertiesdialog.cpp \ - io/crc32.cpp \ - project/projectmodel.cpp \ - io/loadthread.cpp \ - dialogs/loaddialog.cpp \ - debug.cpp \ - io/path.cpp \ - effects/internal/linearfadetransition.cpp \ - effects/internal/transformeffect.cpp \ - effects/internal/solideffect.cpp \ - effects/internal/texteffect.cpp \ - effects/internal/timecodeeffect.cpp \ - effects/internal/audionoiseeffect.cpp \ - effects/internal/paneffect.cpp \ - effects/internal/toneeffect.cpp \ - effects/internal/volumeeffect.cpp \ - effects/internal/crossdissolvetransition.cpp \ - effects/internal/shakeeffect.cpp \ - effects/internal/exponentialfadetransition.cpp \ - effects/internal/logarithmicfadetransition.cpp \ - effects/internal/cornerpineffect.cpp \ - io/math.cpp \ - io/qpainterwrapper.cpp \ - project/effect.cpp \ - project/transition.cpp \ - project/effectrow.cpp \ - project/effectfield.cpp \ - effects/internal/cubetransition.cpp \ - project/effectgizmo.cpp \ - io/clipboard.cpp \ - dialogs/stabilizerdialog.cpp \ - io/avtogl.cpp \ - ui/resizablescrollbar.cpp - -HEADERS += \ - mainwindow.h \ - panels/project.h \ - panels/effectcontrols.h \ - panels/viewer.h \ - panels/timeline.h \ - ui/sourcetable.h \ - dialogs/aboutdialog.h \ - ui/timelinewidget.h \ - project/media.h \ - project/footage.h \ - project/sequence.h \ - project/clip.h \ - playback/playback.h \ - playback/audio.h \ - io/config.h \ - dialogs/newsequencedialog.h \ - ui/viewerwidget.h \ - ui/viewercontainer.h \ - dialogs/exportdialog.h \ - ui/collapsiblewidget.h \ - panels/panels.h \ - playback/cacher.h \ - io/exportthread.h \ - ui/timelinetools.h \ - ui/timelineheader.h \ - io/previewgenerator.h \ - ui/labelslider.h \ - dialogs/preferencesdialog.h \ - ui/audiomonitor.h \ - project/undo.h \ - ui/scrollarea.h \ - ui/comboboxex.h \ - ui/colorbutton.h \ - dialogs/replaceclipmediadialog.h \ - ui/fontcombobox.h \ - ui/checkboxex.h \ - ui/keyframeview.h \ - ui/texteditex.h \ - dialogs/demonotice.h \ - project/marker.h \ - project/selection.h \ - dialogs/speeddialog.h \ - dialogs/mediapropertiesdialog.h \ - io/crc32.h \ - project/projectmodel.h \ - io/loadthread.h \ - dialogs/loaddialog.h \ - debug.h \ - io/path.h \ - effects/internal/transformeffect.h \ - effects/internal/solideffect.h \ - effects/internal/texteffect.h \ - effects/internal/timecodeeffect.h \ - effects/internal/audionoiseeffect.h \ - effects/internal/paneffect.h \ - effects/internal/toneeffect.h \ - effects/internal/volumeeffect.h \ - effects/internal/shakeeffect.h \ - effects/internal/linearfadetransition.h \ - effects/internal/crossdissolvetransition.h \ - effects/internal/exponentialfadetransition.h \ - effects/internal/logarithmicfadetransition.h \ - effects/internal/cornerpineffect.h \ - io/math.h \ - io/qpainterwrapper.h \ - project/effect.h \ - project/transition.h \ - project/effectrow.h \ - project/effectfield.h \ - effects/internal/cubetransition.h \ - project/effectgizmo.h \ - io/clipboard.h \ - dialogs/stabilizerdialog.h \ - io/avtogl.h \ - ui/resizablescrollbar.h - -FORMS += \ - mainwindow.ui \ - panels/effectcontrols.ui \ - panels/viewer.ui \ - panels/timeline.ui \ - dialogs/aboutdialog.ui \ - dialogs/newsequencedialog.ui \ - dialogs/exportdialog.ui \ - dialogs/preferencesdialog.ui \ - dialogs/demonotice.ui - -win32 { - RC_FILE = icons/resources.rc - LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 -} - -mac { - LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample - ICON = icons/olive.icns - INCLUDEPATH = /usr/local/include -} - -linux { - CONFIG += link_pkgconfig - PKGCONFIG += libavutil libavformat libavcodec libavfilter libswscale libswresample -} - -RESOURCES += \ - icons/icons.qrc +#------------------------------------------------- +# +# Project created by QtCreator 2018-05-11T10:31:59 +# +#------------------------------------------------- + +QT += core gui multimedia opengl + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +mac { + TARGET = Olive +} +!mac { + TARGET = olive-editor +} +TEMPLATE = app + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + panels/project.cpp \ + panels/effectcontrols.cpp \ + panels/viewer.cpp \ + panels/timeline.cpp \ + ui/sourcetable.cpp \ + dialogs/aboutdialog.cpp \ + ui/timelinewidget.cpp \ + project/media.cpp \ + project/footage.cpp \ + project/sequence.cpp \ + project/clip.cpp \ + playback/playback.cpp \ + playback/audio.cpp \ + io/config.cpp \ + dialogs/newsequencedialog.cpp \ + ui/viewerwidget.cpp \ + ui/viewercontainer.cpp \ + dialogs/exportdialog.cpp \ + ui/collapsiblewidget.cpp \ + panels/panels.cpp \ + playback/cacher.cpp \ + io/exportthread.cpp \ + ui/timelineheader.cpp \ + io/previewgenerator.cpp \ + ui/labelslider.cpp \ + dialogs/preferencesdialog.cpp \ + ui/audiomonitor.cpp \ + project/undo.cpp \ + ui/scrollarea.cpp \ + ui/comboboxex.cpp \ + ui/colorbutton.cpp \ + dialogs/replaceclipmediadialog.cpp \ + ui/fontcombobox.cpp \ + ui/checkboxex.cpp \ + ui/keyframeview.cpp \ + ui/texteditex.cpp \ + dialogs/demonotice.cpp \ + project/marker.cpp \ + dialogs/speeddialog.cpp \ + dialogs/mediapropertiesdialog.cpp \ + io/crc32.cpp \ + project/projectmodel.cpp \ + io/loadthread.cpp \ + dialogs/loaddialog.cpp \ + debug.cpp \ + io/path.cpp \ + effects/internal/linearfadetransition.cpp \ + effects/internal/transformeffect.cpp \ + effects/internal/solideffect.cpp \ + effects/internal/texteffect.cpp \ + effects/internal/timecodeeffect.cpp \ + effects/internal/audionoiseeffect.cpp \ + effects/internal/paneffect.cpp \ + effects/internal/toneeffect.cpp \ + effects/internal/volumeeffect.cpp \ + effects/internal/crossdissolvetransition.cpp \ + effects/internal/shakeeffect.cpp \ + effects/internal/exponentialfadetransition.cpp \ + effects/internal/logarithmicfadetransition.cpp \ + effects/internal/cornerpineffect.cpp \ + io/math.cpp \ + io/qpainterwrapper.cpp \ + project/effect.cpp \ + project/transition.cpp \ + project/effectrow.cpp \ + project/effectfield.cpp \ + effects/internal/cubetransition.cpp \ + project/effectgizmo.cpp \ + io/clipboard.cpp \ + dialogs/stabilizerdialog.cpp \ + io/avtogl.cpp \ + ui/resizablescrollbar.cpp \ + ui/sourceiconview.cpp \ + project/sourcescommon.cpp + +HEADERS += \ + mainwindow.h \ + panels/project.h \ + panels/effectcontrols.h \ + panels/viewer.h \ + panels/timeline.h \ + ui/sourcetable.h \ + dialogs/aboutdialog.h \ + ui/timelinewidget.h \ + project/media.h \ + project/footage.h \ + project/sequence.h \ + project/clip.h \ + playback/playback.h \ + playback/audio.h \ + io/config.h \ + dialogs/newsequencedialog.h \ + ui/viewerwidget.h \ + ui/viewercontainer.h \ + dialogs/exportdialog.h \ + ui/collapsiblewidget.h \ + panels/panels.h \ + playback/cacher.h \ + io/exportthread.h \ + ui/timelinetools.h \ + ui/timelineheader.h \ + io/previewgenerator.h \ + ui/labelslider.h \ + dialogs/preferencesdialog.h \ + ui/audiomonitor.h \ + project/undo.h \ + ui/scrollarea.h \ + ui/comboboxex.h \ + ui/colorbutton.h \ + dialogs/replaceclipmediadialog.h \ + ui/fontcombobox.h \ + ui/checkboxex.h \ + ui/keyframeview.h \ + ui/texteditex.h \ + dialogs/demonotice.h \ + project/marker.h \ + project/selection.h \ + dialogs/speeddialog.h \ + dialogs/mediapropertiesdialog.h \ + io/crc32.h \ + project/projectmodel.h \ + io/loadthread.h \ + dialogs/loaddialog.h \ + debug.h \ + io/path.h \ + effects/internal/transformeffect.h \ + effects/internal/solideffect.h \ + effects/internal/texteffect.h \ + effects/internal/timecodeeffect.h \ + effects/internal/audionoiseeffect.h \ + effects/internal/paneffect.h \ + effects/internal/toneeffect.h \ + effects/internal/volumeeffect.h \ + effects/internal/shakeeffect.h \ + effects/internal/linearfadetransition.h \ + effects/internal/crossdissolvetransition.h \ + effects/internal/exponentialfadetransition.h \ + effects/internal/logarithmicfadetransition.h \ + effects/internal/cornerpineffect.h \ + io/math.h \ + io/qpainterwrapper.h \ + project/effect.h \ + project/transition.h \ + project/effectrow.h \ + project/effectfield.h \ + effects/internal/cubetransition.h \ + project/effectgizmo.h \ + io/clipboard.h \ + dialogs/stabilizerdialog.h \ + io/avtogl.h \ + ui/resizablescrollbar.h \ + ui/sourceiconview.h \ + project/sourcescommon.h + +FORMS += \ + mainwindow.ui \ + panels/effectcontrols.ui \ + panels/viewer.ui \ + panels/timeline.ui \ + dialogs/aboutdialog.ui \ + dialogs/newsequencedialog.ui \ + dialogs/exportdialog.ui \ + dialogs/preferencesdialog.ui \ + dialogs/demonotice.ui + +win32 { + RC_FILE = icons/resources.rc + LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 +} + +mac { + LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample + ICON = icons/olive.icns + INCLUDEPATH = /usr/local/include +} + +unix:!mac { + CONFIG += link_pkgconfig + PKGCONFIG += libavutil libavformat libavcodec libavfilter libswscale libswresample +} + +RESOURCES += \ + icons/icons.qrc + +unix:!mac { + metainfo.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.appdata.xml + metainfo.path = $$PREFIX/share/metainfo + desktop.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.desktop + desktop.path = $$PREFIX/share/applications + mime.files = $$PWD/packaging/linux/org.olivevideoeditor.Olive.xml + mime.path = $$PREFIX/share/mime/packages + icon16.files = $$PWD/packaging/linux/icons/16x16/org.olivevideoeditor.Olive.png + icon16.path = $$PREFIX/share/icons/hicolor/16x16/apps + icon32.files = $$PWD/packaging/linux/icons/32x32/org.olivevideoeditor.Olive.png + icon32.path = $$PREFIX/share/icons/hicolor/32x32/apps + icon48.files = $$PWD/packaging/linux/icons/48x48/org.olivevideoeditor.Olive.png + icon48.path = $$PREFIX/share/icons/hicolor/48x48/apps + icon64.files = $$PWD/packaging/linux/icons/64x64/org.olivevideoeditor.Olive.png + icon64.path = $$PREFIX/share/icons/hicolor/64x64/apps + icon128.files = $$PWD/packaging/linux/icons/128x128/org.olivevideoeditor.Olive.png + icon128.path = $$PREFIX/share/icons/hicolor/128x128/apps + icon256.files = $$PWD/packaging/linux/icons/256x256/org.olivevideoeditor.Olive.png + icon256.path = $$PREFIX/share/icons/hicolor/256x256/apps + icon512.files = $$PWD/packaging/linux/icons/512x512/org.olivevideoeditor.Olive.png + icon512.path = $$PREFIX/share/icons/hicolor/512x512/apps + icon1024.files = $$PWD/packaging/linux/icons/1024x1024/org.olivevideoeditor.Olive.png + icon1024.path = $$PREFIX/share/icons/hicolor/1024x1024/apps + INSTALLS += metainfo desktop mime icon16 icon32 icon48 icon64 icon128 icon256 icon512 icon1024 +} diff --git a/packaging/linux/icons/1024x1024/org.olivevideoeditor.Olive.png b/packaging/linux/icons/1024x1024/org.olivevideoeditor.Olive.png new file mode 100644 index 000000000..6ecaaddd7 Binary files /dev/null and b/packaging/linux/icons/1024x1024/org.olivevideoeditor.Olive.png differ diff --git a/packaging/linux/icons/128x128/org.olivevideoeditor.Olive.png b/packaging/linux/icons/128x128/org.olivevideoeditor.Olive.png new file mode 100644 index 000000000..ee0cb4254 Binary files /dev/null and b/packaging/linux/icons/128x128/org.olivevideoeditor.Olive.png differ diff --git a/packaging/linux/icons/16x16/org.olivevideoeditor.Olive.png b/packaging/linux/icons/16x16/org.olivevideoeditor.Olive.png new file mode 100644 index 000000000..23326e41e Binary files /dev/null and b/packaging/linux/icons/16x16/org.olivevideoeditor.Olive.png differ diff --git a/packaging/linux/icons/256x256/org.olivevideoeditor.Olive.png b/packaging/linux/icons/256x256/org.olivevideoeditor.Olive.png new file mode 100644 index 000000000..ed188a27f Binary files /dev/null and b/packaging/linux/icons/256x256/org.olivevideoeditor.Olive.png differ diff --git a/packaging/linux/icons/32x32/org.olivevideoeditor.Olive.png b/packaging/linux/icons/32x32/org.olivevideoeditor.Olive.png new file mode 100644 index 000000000..d0d54f3b1 Binary files /dev/null and b/packaging/linux/icons/32x32/org.olivevideoeditor.Olive.png differ diff --git a/packaging/linux/icons/48x48/org.olivevideoeditor.Olive.png b/packaging/linux/icons/48x48/org.olivevideoeditor.Olive.png new file mode 100644 index 000000000..acaba3b75 Binary files /dev/null and b/packaging/linux/icons/48x48/org.olivevideoeditor.Olive.png differ diff --git a/packaging/linux/icons/512x512/org.olivevideoeditor.Olive.png b/packaging/linux/icons/512x512/org.olivevideoeditor.Olive.png new file mode 100644 index 000000000..477abe40b Binary files /dev/null and b/packaging/linux/icons/512x512/org.olivevideoeditor.Olive.png differ diff --git a/packaging/linux/icons/64x64/org.olivevideoeditor.Olive.png b/packaging/linux/icons/64x64/org.olivevideoeditor.Olive.png new file mode 100644 index 000000000..d5e31af4e Binary files /dev/null and b/packaging/linux/icons/64x64/org.olivevideoeditor.Olive.png differ diff --git a/packaging/linux/org.olivevideoeditor.Olive.appdata.xml b/packaging/linux/org.olivevideoeditor.Olive.appdata.xml new file mode 100644 index 000000000..a1a5c07e2 --- /dev/null +++ b/packaging/linux/org.olivevideoeditor.Olive.appdata.xml @@ -0,0 +1,25 @@ + + + org.olivevideoeditor.Olive + Olive + CC0-1.0 + GPLv3 + Olive Team + NLE video editor + +

+ Olive is a professional open-source NLE video editor. +

+
+ https://www.olivevideoeditor.org + + + http://images.libregraphicsworld.org/video/2018/12/introducing-olive/olive-alpha-main-window.jpg + + + + + + + +
diff --git a/packaging/linux/org.olivevideoeditor.Olive.desktop b/packaging/linux/org.olivevideoeditor.Olive.desktop new file mode 100644 index 000000000..8b0f78fd4 --- /dev/null +++ b/packaging/linux/org.olivevideoeditor.Olive.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=Olive +Comment=Professional open-source NLE video editor +Exec=olive-editor +Icon=org.olivevideoeditor.Olive +Terminal=false +Type=Application +Categories=AudioVideo;Recorder; +MimeType=application/vnd.olive-project; +StartupNotify=true diff --git a/packaging/linux/org.olivevideoeditor.Olive.xml b/packaging/linux/org.olivevideoeditor.Olive.xml new file mode 100644 index 000000000..df8e8e517 --- /dev/null +++ b/packaging/linux/org.olivevideoeditor.Olive.xml @@ -0,0 +1,7 @@ + + + + Olive project + + + diff --git a/panels/project.cpp b/panels/project.cpp index ae105cd47..ce3264a6c 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -1,1100 +1,1187 @@ -#include "project.h" -#include "project/footage.h" - -#include "panels/panels.h" -#include "panels/timeline.h" -#include "panels/viewer.h" -#include "playback/playback.h" -#include "project/effect.h" -#include "project/transition.h" -#include "panels/timeline.h" -#include "project/sequence.h" -#include "project/clip.h" -#include "io/previewgenerator.h" -#include "project/undo.h" -#include "mainwindow.h" -#include "io/config.h" -#include "playback/cacher.h" -#include "dialogs/replaceclipmediadialog.h" -#include "panels/effectcontrols.h" -#include "dialogs/newsequencedialog.h" -#include "dialogs/mediapropertiesdialog.h" -#include "dialogs/loaddialog.h" -#include "io/clipboard.h" -#include "project/media.h" -#include "ui/sourcetable.h" -#include "debug.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -extern "C" { - #include - #include -} - -#define MAXIMUM_RECENT_PROJECTS 10 - -ProjectModel project_model; - -QString autorecovery_filename; -QString project_url = ""; -QStringList recent_projects; -QString recent_proj_file; - -Project::Project(QWidget *parent) : - QDockWidget(parent) -{ - setObjectName("Project"); - resize(504, 371); - QSizePolicy sizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); - sizePolicy.setHeightForWidth(this->sizePolicy().hasHeightForWidth()); - setSizePolicy(sizePolicy); - - QWidget* dockWidgetContents = new QWidget(); - QVBoxLayout* verticalLayout = new QVBoxLayout(dockWidgetContents); - verticalLayout->setContentsMargins(0, 0, 0, 0); - - setWidget(dockWidgetContents); - - source_table = new SourceTable(dockWidgetContents); - source_table->project_parent = this; - source_table->setAcceptDrops(true); - source_table->setEditTriggers(QAbstractItemView::NoEditTriggers); - source_table->setDragDropMode(QAbstractItemView::DragDrop); - source_table->setSelectionMode(QAbstractItemView::ExtendedSelection); - verticalLayout->addWidget(source_table); - //setWidget(source_table); - - //layout->addWidget(source_table); - - sorter = new QSortFilterProxyModel(this); - sorter->setSourceModel(&project_model); - source_table->setModel(sorter); - - //retranslateUi(Project); - setWindowTitle(QApplication::translate("Project", "Project", nullptr)); -} - -Project::~Project() { - delete sorter; -} - -QString Project::get_next_sequence_name(QString start) { - if (start.isEmpty()) start = "Sequence"; - - int n = 1; - bool found = true; - QString name; - while (found) { - found = false; - name = start + " "; - if (n < 10) { - name += "0"; - } - name += QString::number(n); - for (int i=0;iget_name(), name, Qt::CaseInsensitive) == 0) { - found = true; - n++; - break; - } - } - } - return name; -} - -Sequence* create_sequence_from_media(QVector& media_list) { - Sequence* s = new Sequence(); - - s->name = panel_project->get_next_sequence_name(); - - // shitty hardcoded default values - s->width = 1920; - s->height = 1080; - s->frame_rate = 29.97; - s->audio_frequency = 48000; - s->audio_layout = 3; - - bool got_video_values = false; - bool got_audio_values = false; - for (int i=0;iget_type()) { - case MEDIA_TYPE_FOOTAGE: - { - Footage* m = media->to_footage(); - if (m->ready) { - if (!got_video_values) { - for (int j=0;jvideo_tracks.size();j++) { - FootageStream* ms = m->video_tracks.at(j); - s->width = ms->video_width; - s->height = ms->video_height; - if (ms->video_frame_rate != 0) { - s->frame_rate = ms->video_frame_rate; - - if (ms->video_interlacing != VIDEO_PROGRESSIVE) s->frame_rate *= 2; - - // only break with a decent frame rate, otherwise there may be a better candidate - got_video_values = true; - break; - } - } - } - if (!got_audio_values) { - for (int j=0;jaudio_tracks.size();j++) { - FootageStream* ms = m->audio_tracks.at(j); - s->audio_frequency = ms->audio_frequency; - got_audio_values = true; - break; - } - } - } - } - break; - case MEDIA_TYPE_SEQUENCE: - { - Sequence* seq = media->to_sequence(); - s->width = seq->width; - s->height = seq->height; - s->frame_rate = seq->frame_rate; - s->audio_frequency = seq->audio_frequency; - s->audio_layout = seq->audio_layout; - - got_video_values = true; - got_audio_values = true; - } - break; - } - if (got_video_values && got_audio_values) break; - } - - return s; -} - -void Project::duplicate_selected() { - QModelIndexList items = source_table->selectionModel()->selectedRows(); - bool duped = false; - ComboAction* ca = new ComboAction(); - for (int j=0;jget_type() == MEDIA_TYPE_SEQUENCE) { - new_sequence(ca, i->to_sequence()->copy(), false, item_to_media(items.at(j).parent())); - duped = true; - } - } - if (duped) { - undo_stack.push(ca); - } else { - delete ca; - } -} - -void Project::replace_selected_file() { - QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); - if (selected_items.size() == 1) { - Media* item = item_to_media(selected_items.at(0)); - if (item->get_type() == MEDIA_TYPE_FOOTAGE) { - replace_media(item, 0); - } - } -} - -void Project::replace_media(Media* item, QString filename) { - if (filename.isEmpty()) { - filename = QFileDialog::getOpenFileName(this, "Replace '" + item->get_name() + "'", "", "All Files (*)"); - } - if (!filename.isEmpty()) { - ReplaceMediaCommand* rmc = new ReplaceMediaCommand(item, filename); - undo_stack.push(rmc); - } -} - -void Project::replace_clip_media() { - if (sequence == NULL) { - QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to replace clips from.", QMessageBox::Ok); - } else { - QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); - if (selected_items.size() == 1) { - Media* item = item_to_media(selected_items.at(0)); - if (item->get_type() == MEDIA_TYPE_SEQUENCE && sequence == item->to_sequence()) { - QMessageBox::critical(this, "Active sequence selected", "You cannot insert a sequence into itself, so no clips of this media would be in this sequence.", QMessageBox::Ok); - } else { - ReplaceClipMediaDialog dialog(this, source_table, item); - dialog.exec(); - } - } - } -} - -void Project::open_properties() { - QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); - if (selected_items.size() == 1) { - Media* item = item_to_media(selected_items.at(0)); - switch (item->get_type()) { - case MEDIA_TYPE_FOOTAGE: - { - MediaPropertiesDialog mpd(this, item); - mpd.exec(); - } - break; - case MEDIA_TYPE_SEQUENCE: - { - NewSequenceDialog nsd(this); - Sequence* s = item->to_sequence(); - nsd.existing_sequence = s; - nsd.existing_item = item; - nsd.exec(); - } - break; - default: - { - // fall back to renaming - QString new_name = QInputDialog::getText(this, "Rename '" + item->get_name() + "'", "Enter new name:", QLineEdit::Normal, item->get_name()); - if (!new_name.isEmpty()) { - MediaRename* mr = new MediaRename(item, new_name); - undo_stack.push(mr); - } - } - } - } -} - -Media* Project::new_sequence(ComboAction *ca, Sequence *s, bool open, Media* parent) { - if (parent == NULL) parent = project_model.get_root(); - Media* item = new Media(parent); - item->set_sequence(s); - - if (ca != NULL) { - ca->append(new NewSequenceCommand(item, parent)); - if (open) ca->append(new ChangeSequenceAction(s)); - } else { - project_model.appendChild(NULL, item); - if (open) set_sequence(s); - } - return item; -} - -QString Project::get_file_name_from_path(const QString& path) { - return path.mid(path.lastIndexOf('/')+1); -} - -/*Media* Project::new_item() { - Media* item = new Media(0); - //item->setFlags(item->flags() | Qt::ItemIsEditable); - return item; -}*/ - -bool Project::is_focused() { - return source_table->hasFocus(); -} - -Media* Project::new_folder(QString name) { - Media* item = new Media(0); - item->set_folder(); - return item; -} - -Media *Project::item_to_media(const QModelIndex &index) { - return static_cast(sorter->mapToSource(index).internalPointer()); -// return static_cast(index.internalPointer()); -} - -void Project::get_all_media_from_table(QList items, QList& list, int search_type) { - for (int i=0;iget_type() == MEDIA_TYPE_FOLDER) { - QList children; - for (int j=0;jchildCount();j++) { - children.append(item->child(j)); - } - get_all_media_from_table(children, list, search_type); - } else if (search_type == item->get_type() || search_type == -1) { - list.append(item); - } - } -} - -bool delete_clips_in_clipboard_with_media(ComboAction* ca, Media* m) { - int delete_count = 0; - if (clipboard_type == CLIPBOARD_TYPE_CLIP) { - for (int i=0;i(clipboard.at(i)); - if (c->media == m) { - ca->append(new RemoveClipsFromClipboard(i-delete_count)); - delete_count++; - } - } - } - return (delete_count > 0); -} - -void Project::delete_selected_media() { - ComboAction* ca = new ComboAction(); - QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); - QList items; - for (int i=0;isetSortingEnabled(false); - source_table->setSortingEnabled(true); - - // check if media is in use - QVector parents; - QList sequence_items; - QList all_top_level_items; - for (int i=0;i 0) { - QList media_items; - get_all_media_from_table(items, media_items, MEDIA_TYPE_FOOTAGE); - for (int i=0;ito_footage(); - bool confirm_delete = false; - for (int j=0;jto_sequence(); - for (int k=0;kclips.size();k++) { - Clip* c = s->clips.at(k); - if (c != NULL && c->media == item) { - if (!confirm_delete) { - // we found a reference, so we know we'll need to ask if the user wants to delete it - QMessageBox confirm(this); - confirm.setWindowTitle("Delete media in use?"); - confirm.setText("The media '" + media->name + "' is currently used in '" + s->name + "'. Deleting it will remove all instances in the sequence. Are you sure you want to do this?"); - QAbstractButton* yes_button = confirm.addButton(QMessageBox::Yes); - QAbstractButton* skip_button = NULL; - if (items.size() > 1) skip_button = confirm.addButton("Skip", QMessageBox::NoRole); - QAbstractButton* abort_button = confirm.addButton(QMessageBox::Cancel); - confirm.exec(); - if (confirm.clickedButton() == yes_button) { - // remove all clips referencing this media - confirm_delete = true; - redraw = true; - } else if (confirm.clickedButton() == skip_button) { - // remove media item and any folders containing it from the remove list - Media* parent = item; - while (parent != NULL) { - parents.append(parent); - - // re-add item's siblings - for (int m=0;mchildCount();m++) { - Media* child = parent->child(m); - bool found = false; - for (int n=0;nparentItem(); - } - - j = sequence_items.size(); - k = s->clips.size(); - } else if (confirm.clickedButton() == abort_button) { - // break out of loop - i = media_items.size(); - j = sequence_items.size(); - k = s->clips.size(); - - remove = false; - } - } - if (confirm_delete) { - ca->append(new DeleteClipAction(s, k)); - } - } - } - } - if (confirm_delete) { - delete_clips_in_clipboard_with_media(ca, item); - } - - } - } - - // remove - if (remove) { - // remove media and parents - for (int m=0;mappend(new DeleteMediaCommand(items.at(i))); - - if (items.at(i)->get_type() == MEDIA_TYPE_SEQUENCE) { - redraw = true; - - Sequence* s = items.at(i)->to_sequence(); - - if (s == sequence) { - ca->append(new ChangeSequenceAction(NULL)); - } - - if (s == panel_footage_viewer->seq) { - panel_footage_viewer->set_media(NULL); - } - } else if (items.at(i)->get_type() == MEDIA_TYPE_FOOTAGE) { - if (panel_footage_viewer->seq != NULL) { - for (int j=0;jseq->clips.size();j++) { - Clip* c = panel_footage_viewer->seq->clips.at(j); - if (c != NULL) { - if (c->media == items.at(i)->to_object()) { - panel_footage_viewer->set_media(NULL); - } - break; - } - } - } - } - } - undo_stack.push(ca); - - // redraw clips - if (redraw) { - update_ui(true); - } - } else { - delete ca; - } -} - -void Project::start_preview_generator(Media* item, bool replacing) { - // set up throbber animation - MediaThrobber* throbber = new MediaThrobber(item); - throbber->moveToThread(QApplication::instance()->thread()); - item->throbber = throbber; - QMetaObject::invokeMethod(throbber, "start", Qt::QueuedConnection); - - PreviewGenerator* pg = new PreviewGenerator(item, item->to_footage(), replacing); - item->to_footage()->preview_gen = pg; - connect(pg, SIGNAL(set_icon(int, bool)), throbber, SLOT(stop(int, bool))); - pg->start(QThread::LowPriority); -} - -void Project::process_file_list(QStringList& files, bool recursive, Media* replace, Media* parent) { - bool imported = false; - - QVector image_sequence_urls; - QVector image_sequence_importassequence; - QStringList image_sequence_formats = config.img_seq_formats.split("|"); - - if (!recursive) last_imported_media.clear(); - - bool create_undo_action = (!recursive && replace == NULL); - ComboAction* ca; - if (create_undo_action) ca = new ComboAction(); - - for (int i=0;iappend(new AddMediaCommand(folder, parent)); - } else { - project_model.appendChild(parent, folder); - } - - imported = true; - } else if (!files.at(i).isEmpty()) { - QString file(files.at(i)); - bool skip = false; - - /* Heuristic to determine whether file is part of an image sequence */ - - // check file extension (assume it's not a - - int lastcharindex = file.lastIndexOf("."); - bool found = true; - if (lastcharindex != -1 && lastcharindex > file.lastIndexOf('/')) { - // image_sequence_formats - found = false; - QString ext = file.mid(lastcharindex+1); - for (int j=0;jto_footage(); - m->reset(); - } else { - item = new Media(parent); - m = new Footage(); - } - - m->using_inout = false; - m->url = file; - m->name = get_file_name_from_path(files.at(i)); - - item->set_footage(m); - - // generate waveform/thumbnail in another thread - start_preview_generator(item, replace != NULL); - - last_imported_media.append(item); - - if (replace == NULL) { - if (create_undo_action) { - ca->append(new AddMediaCommand(item, parent)); - } else { - project_model.appendChild(parent, item); - } - } - - imported = true; - } - } - } - if (create_undo_action) { - if (imported) { - undo_stack.push(ca); - } else { - delete ca; - } - } -} - -Media* Project::get_selected_folder() { - // if one item is selected and it's a folder, return it - QModelIndexList selected_items = source_table->selectionModel()->selectedRows(); - if (selected_items.size() == 1) { - Media* m = item_to_media(selected_items.at(0)); - if (m->get_type() == MEDIA_TYPE_FOLDER) return m; - } - return NULL; -} - -bool Project::reveal_media(void *media, QModelIndex parent) { - for (int i=0;iget_type() == MEDIA_TYPE_FOLDER) { - if (reveal_media(media, item)) return true; - } else if (m->to_object() == media) { - // expand all folders leading to this media - QModelIndex hierarchy = item.parent(); - while (hierarchy.isValid()) { - source_table->setExpanded(hierarchy, true); - hierarchy = hierarchy.parent(); - } - - // select item - source_table->selectionModel()->select(item, QItemSelectionModel::Select); - - return true; - } - } - - return false; -} - -void Project::import_dialog() { - QFileDialog fd(this, "Import media...", "", "All Files (*)"); - fd.setFileMode(QFileDialog::ExistingFiles); - - if (fd.exec()) { - QStringList files = fd.selectedFiles(); - process_file_list(files, false, NULL, get_selected_folder()); - } -} - -void Project::delete_clips_using_selected_media() { - if (sequence == NULL) { - QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to delete clips from.", QMessageBox::Ok); - } else { - ComboAction* ca = new ComboAction(); - bool deleted = false; - QModelIndexList items = source_table->selectionModel()->selectedRows(); - for (int i=0;iclips.size();i++) { - Clip* c = sequence->clips.at(i); - if (c != NULL) { - for (int j=0;jmedia == m) { - ca->append(new DeleteClipAction(sequence, i)); - deleted = true; - } - } - } - } - for (int j=0;jclear_effects(true); - - // delete sequences first because it's important to close all the clips before deleting the media - QVector sequences = list_all_project_sequences(); - for (int i=0;ito_sequence(); - sequences.at(i)->set_sequence(NULL); - } - - // delete everything else - project_model.clear(); -} - -void Project::new_project() { - // clear existing project - set_sequence(NULL); - panel_footage_viewer->set_media(NULL); - clear(); - mainWindow->setWindowModified(false); -} - -void Project::load_project(bool autorecovery) { - new_project(); - - LoadDialog ld(this, autorecovery); - ld.exec(); -} - -void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, const QModelIndex& parent) { - bool root = (!parent.parent().isValid()); - for (int i=0;iget_type()) { - if (m->get_type() == MEDIA_TYPE_FOLDER) { - if (set_ids_only) { - m->temp_id = folder_id; // saves a temporary ID for matching in the project file - folder_id++; - } else { - // if we're saving folders, save the folder - stream.writeStartElement("folder"); - stream.writeAttribute("name", m->get_name()); - stream.writeAttribute("id", QString::number(m->temp_id)); - if (!item.parent().isValid()) { - stream.writeAttribute("parent", "0"); - } else { - stream.writeAttribute("parent", QString::number(project_model.getItem(item.parent())->temp_id)); - } - stream.writeEndElement(); - } - // save_folder(stream, item, type, set_ids_only); - } else { - int folder = root ? 0 : project_model.getItem(parent)->temp_id; - if (type == MEDIA_TYPE_FOOTAGE) { - Footage* f = m->to_footage(); - f->save_id = media_id; - stream.writeStartElement("footage"); - stream.writeAttribute("id", QString::number(media_id)); - stream.writeAttribute("folder", QString::number(folder)); - stream.writeAttribute("name", f->name); - stream.writeAttribute("url", proj_dir.relativeFilePath(f->url)); - stream.writeAttribute("duration", QString::number(f->length)); - stream.writeAttribute("using_inout", QString::number(f->using_inout)); - stream.writeAttribute("in", QString::number(f->in)); - stream.writeAttribute("out", QString::number(f->out)); - for (int j=0;jvideo_tracks.size();j++) { - FootageStream* ms = f->video_tracks.at(j); - stream.writeStartElement("video"); - stream.writeAttribute("id", QString::number(ms->file_index)); - stream.writeAttribute("width", QString::number(ms->video_width)); - stream.writeAttribute("height", QString::number(ms->video_height)); - stream.writeAttribute("framerate", QString::number(ms->video_frame_rate, 'f', 10)); - stream.writeAttribute("infinite", QString::number(ms->infinite_length)); - stream.writeEndElement(); - } - for (int j=0;jaudio_tracks.size();j++) { - FootageStream* ms = f->audio_tracks.at(j); - stream.writeStartElement("audio"); - stream.writeAttribute("id", QString::number(ms->file_index)); - stream.writeAttribute("channels", QString::number(ms->audio_channels)); - stream.writeAttribute("layout", QString::number(ms->audio_layout)); - stream.writeAttribute("frequency", QString::number(ms->audio_frequency)); - stream.writeEndElement(); - } - stream.writeEndElement(); - media_id++; - } else if (type == MEDIA_TYPE_SEQUENCE) { - Sequence* s = m->to_sequence(); - if (set_ids_only) { - s->save_id = sequence_id; - sequence_id++; - } else { - stream.writeStartElement("sequence"); - stream.writeAttribute("id", QString::number(s->save_id)); - stream.writeAttribute("folder", QString::number(folder)); - stream.writeAttribute("name", s->name); - stream.writeAttribute("width", QString::number(s->width)); - stream.writeAttribute("height", QString::number(s->height)); - stream.writeAttribute("framerate", QString::number(s->frame_rate, 'f', 10)); - stream.writeAttribute("afreq", QString::number(s->audio_frequency)); - stream.writeAttribute("alayout", QString::number(s->audio_layout)); - if (s == sequence) { - stream.writeAttribute("open", "1"); - } - stream.writeAttribute("workarea", QString::number(s->using_workarea)); - stream.writeAttribute("workareaIn", QString::number(s->workarea_in)); - stream.writeAttribute("workareaOut", QString::number(s->workarea_out)); - - for (int j=0;jtransitions.size();j++) { - Transition* t = s->transitions.at(j); - if (t != NULL) { - stream.writeStartElement("transition"); - stream.writeAttribute("id", QString::number(j)); - stream.writeAttribute("length", QString::number(t->get_true_length())); - t->save(stream); - stream.writeEndElement(); // transition - } - } - - for (int j=0;jclips.size();j++) { - Clip* c = s->clips.at(j); - if (c != NULL) { - stream.writeStartElement("clip"); // clip - stream.writeAttribute("id", QString::number(j)); - stream.writeAttribute("enabled", QString::number(c->enabled)); - stream.writeAttribute("name", c->name); - stream.writeAttribute("clipin", QString::number(c->clip_in)); - stream.writeAttribute("in", QString::number(c->timeline_in)); - stream.writeAttribute("out", QString::number(c->timeline_out)); - stream.writeAttribute("track", QString::number(c->track)); - stream.writeAttribute("opening", QString::number(c->opening_transition)); - stream.writeAttribute("closing", QString::number(c->closing_transition)); - - stream.writeAttribute("r", QString::number(c->color_r)); - stream.writeAttribute("g", QString::number(c->color_g)); - stream.writeAttribute("b", QString::number(c->color_b)); - - stream.writeAttribute("autoscale", QString::number(c->autoscale)); - stream.writeAttribute("speed", QString::number(c->speed, 'f', 10)); - stream.writeAttribute("maintainpitch", QString::number(c->maintain_audio_pitch)); - stream.writeAttribute("reverse", QString::number(c->reverse)); - - if (c->media != NULL) { - stream.writeAttribute("type", QString::number(c->media->get_type())); - switch (c->media->get_type()) { - case MEDIA_TYPE_FOOTAGE: - stream.writeAttribute("media", QString::number(c->media->to_footage()->save_id)); - stream.writeAttribute("stream", QString::number(c->media_stream)); - break; - case MEDIA_TYPE_SEQUENCE: - stream.writeAttribute("sequence", QString::number(c->media->to_sequence()->save_id)); - break; - } - } - - stream.writeStartElement("linked"); // linked - for (int k=0;klinked.size();k++) { - stream.writeStartElement("link"); // link - stream.writeAttribute("id", QString::number(c->linked.at(k))); - stream.writeEndElement(); // link - } - stream.writeEndElement(); // linked - - for (int k=0;keffects.size();k++) { - stream.writeStartElement("effect"); // effect - c->effects.at(k)->save(stream); - stream.writeEndElement(); // effect - } - - stream.writeEndElement(); // clip - } - } - for (int j=0;jmarkers.size();j++) { - stream.writeStartElement("marker"); - stream.writeAttribute("frame", QString::number(s->markers.at(j).frame)); - stream.writeAttribute("name", s->markers.at(j).name); - stream.writeEndElement(); - } - stream.writeEndElement(); - } - } - } - } - - if (m->get_type() == MEDIA_TYPE_FOLDER) { - save_folder(stream, type, set_ids_only, item); - } - } -} - -void Project::save_project(bool autorecovery) { - folder_id = 1; - media_id = 1; - sequence_id = 1; - - QFile file(autorecovery ? autorecovery_filename : project_url); - if (!file.open(QIODevice::WriteOnly/* | QIODevice::Text*/)) { - dout << "[ERROR] Could not open file"; - return; - } - - QXmlStreamWriter stream(&file); - stream.setAutoFormatting(true); - stream.writeStartDocument(); // doc - - stream.writeStartElement("project"); // project - - stream.writeTextElement("version", QString::number(SAVE_VERSION)); - - stream.writeTextElement("url", project_url); - proj_dir = QFileInfo(project_url).absoluteDir(); - - save_folder(stream, MEDIA_TYPE_FOLDER, true); - - stream.writeStartElement("folders"); // folders - save_folder(stream, MEDIA_TYPE_FOLDER, false); - stream.writeEndElement(); // folders - - stream.writeStartElement("media"); // media - save_folder(stream, MEDIA_TYPE_FOOTAGE, false); - stream.writeEndElement(); // media - - save_folder(stream, MEDIA_TYPE_SEQUENCE, true); - - stream.writeStartElement("sequences"); // sequences - save_folder(stream, MEDIA_TYPE_SEQUENCE, false); - stream.writeEndElement();// sequences - - stream.writeEndElement(); // project - - stream.writeEndDocument(); // doc - - file.close(); - - if (!autorecovery) { - add_recent_project(project_url); - mainWindow->setWindowModified(false); - } -} - -void Project::save_recent_projects() { - // save to file - QFile f(recent_proj_file); - if (f.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) { - QTextStream out(&f); - for (int i=0;i 0) { - out << "\n"; - } - out << recent_projects.at(i); - } - f.close(); - } else { - dout << "[WARNING] Could not save recent projects"; - } -} - -void Project::clear_recent_projects() { - recent_projects.clear(); - save_recent_projects(); -} - -void Project::add_recent_project(QString url) { - bool found = false; - for (int i=0;i MAXIMUM_RECENT_PROJECTS) { - recent_projects.removeLast(); - } - } - save_recent_projects(); -} - -void Project::list_all_sequences_worker(QVector* list, Media* parent) { - for (int i=0;iget_type()) { - case MEDIA_TYPE_SEQUENCE: - list->append(item); - break; - case MEDIA_TYPE_FOLDER: - list_all_sequences_worker(list, item); - break; - } - } -} - -QVector Project::list_all_project_sequences() { - QVector list; - list_all_sequences_worker(&list, NULL); - return list; -} - -#define THROBBER_LIMIT 20 -#define THROBBER_SIZE 50 - -MediaThrobber::MediaThrobber(Media *i) : pixmap(":/icons/throbber.png"), animation(0), item(i), animator(NULL) {} - -void MediaThrobber::start() { - // set up throbber - animation_update(); - animator = new QTimer(this); - animator->setInterval(20); - connect(animator, SIGNAL(timeout()), this, SLOT(animation_update())); - animator->start(); -} - -void MediaThrobber::animation_update() { - if (animation == THROBBER_LIMIT) { - animation = 0; - } - project_model.set_icon(item, QIcon(pixmap.copy(THROBBER_SIZE*animation, 0, THROBBER_SIZE, THROBBER_SIZE))); - animation++; -} - -void MediaThrobber::stop(int icon_type, bool replace) { - if (animator != NULL) { - animator->stop(); - delete animator; - } - - switch (icon_type) { - case ICON_TYPE_VIDEO: project_model.set_icon(item, QIcon(":/icons/videosource.png")); break; - case ICON_TYPE_AUDIO: project_model.set_icon(item, QIcon(":/icons/audiosource.png")); break; - case ICON_TYPE_IMAGE: project_model.set_icon(item, QIcon(":/icons/imagesource.png")); break; - case ICON_TYPE_ERROR: project_model.set_icon(item, QIcon(":/icons/error.png")); break; - } - - // refresh all clips - QVector sequences = panel_project->list_all_project_sequences(); - for (int i=0;ito_sequence(); - for (int j=0;jclips.size();j++) { - Clip* c = s->clips.at(j); - if (c != NULL) { - c->refresh(); - } - } - } - - // redraw clips - update_ui(replace); - - panel_project->source_table->viewport()->update(); - item->throbber = NULL; - deleteLater(); -} +#include "project.h" +#include "project/footage.h" + +#include "panels/panels.h" +#include "panels/timeline.h" +#include "panels/viewer.h" +#include "playback/playback.h" +#include "project/effect.h" +#include "project/transition.h" +#include "panels/timeline.h" +#include "project/sequence.h" +#include "project/clip.h" +#include "io/previewgenerator.h" +#include "project/undo.h" +#include "mainwindow.h" +#include "io/config.h" +#include "playback/cacher.h" +#include "dialogs/replaceclipmediadialog.h" +#include "panels/effectcontrols.h" +#include "dialogs/newsequencedialog.h" +#include "dialogs/mediapropertiesdialog.h" +#include "dialogs/loaddialog.h" +#include "io/clipboard.h" +#include "project/media.h" +#include "ui/sourcetable.h" +#include "ui/sourceiconview.h" +#include "project/sourcescommon.h" +#include "debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { + #include + #include +} + +#define MAXIMUM_RECENT_PROJECTS 10 + +ProjectModel project_model; + +QString autorecovery_filename; +QString project_url = ""; +QStringList recent_projects; +QString recent_proj_file; + +Project::Project(QWidget *parent) : + QDockWidget(parent) +{ + setObjectName("Project"); + resize(504, 371); + QSizePolicy sizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(this->sizePolicy().hasHeightForWidth()); + setSizePolicy(sizePolicy); + + QWidget* dockWidgetContents = new QWidget(); + QVBoxLayout* verticalLayout = new QVBoxLayout(dockWidgetContents); + verticalLayout->setContentsMargins(0, 0, 0, 0); + verticalLayout->setSpacing(0); + + setWidget(dockWidgetContents); + + sources_common = new SourcesCommon(this); + + sorter = new QSortFilterProxyModel(this); + sorter->setSourceModel(&project_model); + + tree_view = new SourceTable(dockWidgetContents); + tree_view->project_parent = this; + tree_view->setAcceptDrops(true); + tree_view->setEditTriggers(QAbstractItemView::NoEditTriggers); + tree_view->setDragDropMode(QAbstractItemView::DragDrop); + tree_view->setSelectionMode(QAbstractItemView::ExtendedSelection); + tree_view->setModel(sorter); + verticalLayout->addWidget(tree_view); + + icon_view_container = new QWidget(); + + QVBoxLayout* icon_view_container_layout = new QVBoxLayout(); + icon_view_container_layout->setMargin(0); + icon_view_container_layout->setSpacing(0); + icon_view_container->setLayout(icon_view_container_layout); + + QHBoxLayout* icon_view_controls = new QHBoxLayout(); + icon_view_controls->setMargin(0); + icon_view_controls->setSpacing(0); + + QIcon directory_up_button; + directory_up_button.addFile(":/icons/dirup.png", QSize(), QIcon::Normal); + directory_up_button.addFile(":/icons/dirup-disabled.png", QSize(), QIcon::Disabled); + + directory_up = new QPushButton(); + directory_up->setIcon(directory_up_button); + directory_up->setEnabled(false); + icon_view_controls->addWidget(directory_up); + + icon_view_controls->addStretch(); + + QSlider* icon_size_slider = new QSlider(Qt::Horizontal); + icon_size_slider->setMinimum(16); + icon_size_slider->setMaximum(120); + icon_view_controls->addWidget(icon_size_slider); + connect(icon_size_slider, SIGNAL(valueChanged(int)), this, SLOT(set_icon_view_size(int))); + + icon_view_container_layout->addLayout(icon_view_controls); + + icon_view = new SourceIconView(dockWidgetContents); + icon_view->project_parent = this; + icon_view->setModel(sorter); + icon_view->setIconSize(QSize(100, 100)); + icon_view->setViewMode(QListView::IconMode); + icon_view->setUniformItemSizes(true); + icon_view_container_layout->addWidget(icon_view); + + icon_size_slider->setValue(icon_view->iconSize().height()); + + verticalLayout->addWidget(icon_view_container); + + connect(directory_up, SIGNAL(clicked(bool)), this, SLOT(go_up_dir())); + connect(icon_view, SIGNAL(changed_root()), this, SLOT(set_up_dir_enabled())); + + //retranslateUi(Project); + setWindowTitle(QApplication::translate("Project", "Project", nullptr)); + + update_view_type(); +} + +Project::~Project() { + delete sorter; +} + +QString Project::get_next_sequence_name(QString start) { + if (start.isEmpty()) start = "Sequence"; + + int n = 1; + bool found = true; + QString name; + while (found) { + found = false; + name = start + " "; + if (n < 10) { + name += "0"; + } + name += QString::number(n); + for (int i=0;iget_name(), name, Qt::CaseInsensitive) == 0) { + found = true; + n++; + break; + } + } + } + return name; +} + +Sequence* create_sequence_from_media(QVector& media_list) { + Sequence* s = new Sequence(); + + s->name = panel_project->get_next_sequence_name(); + + // shitty hardcoded default values + s->width = 1920; + s->height = 1080; + s->frame_rate = 29.97; + s->audio_frequency = 48000; + s->audio_layout = 3; + + bool got_video_values = false; + bool got_audio_values = false; + for (int i=0;iget_type()) { + case MEDIA_TYPE_FOOTAGE: + { + Footage* m = media->to_footage(); + if (m->ready) { + if (!got_video_values) { + for (int j=0;jvideo_tracks.size();j++) { + FootageStream* ms = m->video_tracks.at(j); + s->width = ms->video_width; + s->height = ms->video_height; + if (ms->video_frame_rate != 0) { + s->frame_rate = ms->video_frame_rate; + + if (ms->video_interlacing != VIDEO_PROGRESSIVE) s->frame_rate *= 2; + + // only break with a decent frame rate, otherwise there may be a better candidate + got_video_values = true; + break; + } + } + } + if (!got_audio_values) { + for (int j=0;jaudio_tracks.size();j++) { + FootageStream* ms = m->audio_tracks.at(j); + s->audio_frequency = ms->audio_frequency; + got_audio_values = true; + break; + } + } + } + } + break; + case MEDIA_TYPE_SEQUENCE: + { + Sequence* seq = media->to_sequence(); + s->width = seq->width; + s->height = seq->height; + s->frame_rate = seq->frame_rate; + s->audio_frequency = seq->audio_frequency; + s->audio_layout = seq->audio_layout; + + got_video_values = true; + got_audio_values = true; + } + break; + } + if (got_video_values && got_audio_values) break; + } + + return s; +} + +void Project::duplicate_selected() { + QModelIndexList items = tree_view->selectionModel()->selectedRows(); + bool duped = false; + ComboAction* ca = new ComboAction(); + for (int j=0;jget_type() == MEDIA_TYPE_SEQUENCE) { + new_sequence(ca, i->to_sequence()->copy(), false, item_to_media(items.at(j).parent())); + duped = true; + } + } + if (duped) { + undo_stack.push(ca); + } else { + delete ca; + } +} + +void Project::replace_selected_file() { + QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* item = item_to_media(selected_items.at(0)); + if (item->get_type() == MEDIA_TYPE_FOOTAGE) { + replace_media(item, 0); + } + } +} + +void Project::replace_media(Media* item, QString filename) { + if (filename.isEmpty()) { + filename = QFileDialog::getOpenFileName(this, "Replace '" + item->get_name() + "'", "", "All Files (*)"); + } + if (!filename.isEmpty()) { + ReplaceMediaCommand* rmc = new ReplaceMediaCommand(item, filename); + undo_stack.push(rmc); + } +} + +void Project::replace_clip_media() { + if (sequence == NULL) { + QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to replace clips from.", QMessageBox::Ok); + } else { + QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* item = item_to_media(selected_items.at(0)); + if (item->get_type() == MEDIA_TYPE_SEQUENCE && sequence == item->to_sequence()) { + QMessageBox::critical(this, "Active sequence selected", "You cannot insert a sequence into itself, so no clips of this media would be in this sequence.", QMessageBox::Ok); + } else { + ReplaceClipMediaDialog dialog(this, tree_view, item); + dialog.exec(); + } + } + } +} + +void Project::open_properties() { + QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* item = item_to_media(selected_items.at(0)); + switch (item->get_type()) { + case MEDIA_TYPE_FOOTAGE: + { + MediaPropertiesDialog mpd(this, item); + mpd.exec(); + } + break; + case MEDIA_TYPE_SEQUENCE: + { + NewSequenceDialog nsd(this); + Sequence* s = item->to_sequence(); + nsd.existing_sequence = s; + nsd.existing_item = item; + nsd.exec(); + } + break; + default: + { + // fall back to renaming + QString new_name = QInputDialog::getText(this, "Rename '" + item->get_name() + "'", "Enter new name:", QLineEdit::Normal, item->get_name()); + if (!new_name.isEmpty()) { + MediaRename* mr = new MediaRename(item, new_name); + undo_stack.push(mr); + } + } + } + } +} + +Media* Project::new_sequence(ComboAction *ca, Sequence *s, bool open, Media* parent) { + if (parent == NULL) parent = project_model.get_root(); + Media* item = new Media(parent); + item->set_sequence(s); + + if (ca != NULL) { + ca->append(new NewSequenceCommand(item, parent)); + if (open) ca->append(new ChangeSequenceAction(s)); + } else { + project_model.appendChild(NULL, item); + if (open) set_sequence(s); + } + return item; +} + +QString Project::get_file_name_from_path(const QString& path) { + return path.mid(path.lastIndexOf('/')+1); +} + +/*Media* Project::new_item() { + Media* item = new Media(0); + //item->setFlags(item->flags() | Qt::ItemIsEditable); + return item; +}*/ + +bool Project::is_focused() { + return tree_view->hasFocus(); +} + +Media* Project::new_folder(QString name) { + Media* item = new Media(0); + item->set_folder(); + return item; +} + +Media *Project::item_to_media(const QModelIndex &index) { + return static_cast(sorter->mapToSource(index).internalPointer()); +// return static_cast(index.internalPointer()); +} + +void Project::get_all_media_from_table(QList items, QList& list, int search_type) { + for (int i=0;iget_type() == MEDIA_TYPE_FOLDER) { + QList children; + for (int j=0;jchildCount();j++) { + children.append(item->child(j)); + } + get_all_media_from_table(children, list, search_type); + } else if (search_type == item->get_type() || search_type == -1) { + list.append(item); + } + } +} + +bool delete_clips_in_clipboard_with_media(ComboAction* ca, Media* m) { + int delete_count = 0; + if (clipboard_type == CLIPBOARD_TYPE_CLIP) { + for (int i=0;i(clipboard.at(i)); + if (c->media == m) { + ca->append(new RemoveClipsFromClipboard(i-delete_count)); + delete_count++; + } + } + } + return (delete_count > 0); +} + +void Project::delete_selected_media() { + ComboAction* ca = new ComboAction(); + QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); + QList items; + for (int i=0;isetSortingEnabled(false); + tree_view->setSortingEnabled(true); + + // check if media is in use + QVector parents; + QList sequence_items; + QList all_top_level_items; + for (int i=0;i 0) { + QList media_items; + get_all_media_from_table(items, media_items, MEDIA_TYPE_FOOTAGE); + for (int i=0;ito_footage(); + bool confirm_delete = false; + for (int j=0;jto_sequence(); + for (int k=0;kclips.size();k++) { + Clip* c = s->clips.at(k); + if (c != NULL && c->media == item) { + if (!confirm_delete) { + // we found a reference, so we know we'll need to ask if the user wants to delete it + QMessageBox confirm(this); + confirm.setWindowTitle("Delete media in use?"); + confirm.setText("The media '" + media->name + "' is currently used in '" + s->name + "'. Deleting it will remove all instances in the sequence. Are you sure you want to do this?"); + QAbstractButton* yes_button = confirm.addButton(QMessageBox::Yes); + QAbstractButton* skip_button = NULL; + if (items.size() > 1) skip_button = confirm.addButton("Skip", QMessageBox::NoRole); + QAbstractButton* abort_button = confirm.addButton(QMessageBox::Cancel); + confirm.exec(); + if (confirm.clickedButton() == yes_button) { + // remove all clips referencing this media + confirm_delete = true; + redraw = true; + } else if (confirm.clickedButton() == skip_button) { + // remove media item and any folders containing it from the remove list + Media* parent = item; + while (parent != NULL) { + parents.append(parent); + + // re-add item's siblings + for (int m=0;mchildCount();m++) { + Media* child = parent->child(m); + bool found = false; + for (int n=0;nparentItem(); + } + + j = sequence_items.size(); + k = s->clips.size(); + } else if (confirm.clickedButton() == abort_button) { + // break out of loop + i = media_items.size(); + j = sequence_items.size(); + k = s->clips.size(); + + remove = false; + } + } + if (confirm_delete) { + ca->append(new DeleteClipAction(s, k)); + } + } + } + } + if (confirm_delete) { + delete_clips_in_clipboard_with_media(ca, item); + } + + } + } + + // remove + if (remove) { + // remove media and parents + for (int m=0;mappend(new DeleteMediaCommand(items.at(i))); + + if (items.at(i)->get_type() == MEDIA_TYPE_SEQUENCE) { + redraw = true; + + Sequence* s = items.at(i)->to_sequence(); + + if (s == sequence) { + ca->append(new ChangeSequenceAction(NULL)); + } + + if (s == panel_footage_viewer->seq) { + panel_footage_viewer->set_media(NULL); + } + } else if (items.at(i)->get_type() == MEDIA_TYPE_FOOTAGE) { + if (panel_footage_viewer->seq != NULL) { + for (int j=0;jseq->clips.size();j++) { + Clip* c = panel_footage_viewer->seq->clips.at(j); + if (c != NULL) { + if (c->media == items.at(i)->to_object()) { + panel_footage_viewer->set_media(NULL); + } + break; + } + } + } + } + } + undo_stack.push(ca); + + // redraw clips + if (redraw) { + update_ui(true); + } + } else { + delete ca; + } +} + +void Project::start_preview_generator(Media* item, bool replacing) { + // set up throbber animation + MediaThrobber* throbber = new MediaThrobber(item); + throbber->moveToThread(QApplication::instance()->thread()); + item->throbber = throbber; + QMetaObject::invokeMethod(throbber, "start", Qt::QueuedConnection); + + PreviewGenerator* pg = new PreviewGenerator(item, item->to_footage(), replacing); + item->to_footage()->preview_gen = pg; + connect(pg, SIGNAL(set_icon(int, bool)), throbber, SLOT(stop(int, bool))); + pg->start(QThread::LowPriority); +} + +void Project::process_file_list(QStringList& files, bool recursive, Media* replace, Media* parent) { + bool imported = false; + + QVector image_sequence_urls; + QVector image_sequence_importassequence; + QStringList image_sequence_formats = config.img_seq_formats.split("|"); + + if (!recursive) last_imported_media.clear(); + + bool create_undo_action = (!recursive && replace == NULL); + ComboAction* ca; + if (create_undo_action) ca = new ComboAction(); + + for (int i=0;iappend(new AddMediaCommand(folder, parent)); + } else { + project_model.appendChild(parent, folder); + } + + imported = true; + } else if (!files.at(i).isEmpty()) { + QString file(files.at(i)); + bool skip = false; + + /* Heuristic to determine whether file is part of an image sequence */ + + // check file extension (assume it's not a + + int lastcharindex = file.lastIndexOf("."); + bool found = true; + if (lastcharindex != -1 && lastcharindex > file.lastIndexOf('/')) { + // image_sequence_formats + found = false; + QString ext = file.mid(lastcharindex+1); + for (int j=0;jto_footage(); + m->reset(); + } else { + item = new Media(parent); + m = new Footage(); + } + + m->using_inout = false; + m->url = file; + m->name = get_file_name_from_path(files.at(i)); + + item->set_footage(m); + + // generate waveform/thumbnail in another thread + start_preview_generator(item, replace != NULL); + + last_imported_media.append(item); + + if (replace == NULL) { + if (create_undo_action) { + ca->append(new AddMediaCommand(item, parent)); + } else { + project_model.appendChild(parent, item); + } + } + + imported = true; + } + } + } + if (create_undo_action) { + if (imported) { + undo_stack.push(ca); + } else { + delete ca; + } + } +} + +Media* Project::get_selected_folder() { + // if one item is selected and it's a folder, return it + QModelIndexList selected_items = tree_view->selectionModel()->selectedRows(); + if (selected_items.size() == 1) { + Media* m = item_to_media(selected_items.at(0)); + if (m->get_type() == MEDIA_TYPE_FOLDER) return m; + } + return NULL; +} + +bool Project::reveal_media(Media *media, QModelIndex parent) { + for (int i=0;iget_type() == MEDIA_TYPE_FOLDER) { + if (reveal_media(media, item)) return true; + } else if (m == media) { + // expand all folders leading to this media + QModelIndex sorted_index = sorter->mapFromSource(item); + + QModelIndex hierarchy = sorted_index.parent(); + while (hierarchy.isValid()) { + tree_view->setExpanded(hierarchy, true); + hierarchy = hierarchy.parent(); + } + + // select item + tree_view->selectionModel()->select(sorted_index, QItemSelectionModel::Select); + + return true; + } + } + + return false; +} + +void Project::import_dialog() { + QFileDialog fd(this, "Import media...", "", "All Files (*)"); + fd.setFileMode(QFileDialog::ExistingFiles); + + if (fd.exec()) { + QStringList files = fd.selectedFiles(); + process_file_list(files, false, NULL, get_selected_folder()); + } +} + +void Project::delete_clips_using_selected_media() { + if (sequence == NULL) { + QMessageBox::critical(this, "No active sequence", "No sequence is active, please open the sequence you want to delete clips from.", QMessageBox::Ok); + } else { + ComboAction* ca = new ComboAction(); + bool deleted = false; + QModelIndexList items = tree_view->selectionModel()->selectedRows(); + for (int i=0;iclips.size();i++) { + Clip* c = sequence->clips.at(i); + if (c != NULL) { + for (int j=0;jmedia == m) { + ca->append(new DeleteClipAction(sequence, i)); + deleted = true; + } + } + } + } + for (int j=0;jclear_effects(true); + + // delete sequences first because it's important to close all the clips before deleting the media + QVector sequences = list_all_project_sequences(); + for (int i=0;ito_sequence(); + sequences.at(i)->set_sequence(NULL); + } + + // delete everything else + project_model.clear(); +} + +void Project::new_project() { + // clear existing project + set_sequence(NULL); + panel_footage_viewer->set_media(NULL); + clear(); + mainWindow->setWindowModified(false); +} + +void Project::load_project(bool autorecovery) { + new_project(); + + LoadDialog ld(this, autorecovery); + ld.exec(); +} + +void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, const QModelIndex& parent) { + bool root = (!parent.parent().isValid()); + for (int i=0;iget_type()) { + if (m->get_type() == MEDIA_TYPE_FOLDER) { + if (set_ids_only) { + m->temp_id = folder_id; // saves a temporary ID for matching in the project file + folder_id++; + } else { + // if we're saving folders, save the folder + stream.writeStartElement("folder"); + stream.writeAttribute("name", m->get_name()); + stream.writeAttribute("id", QString::number(m->temp_id)); + if (!item.parent().isValid()) { + stream.writeAttribute("parent", "0"); + } else { + stream.writeAttribute("parent", QString::number(project_model.getItem(item.parent())->temp_id)); + } + stream.writeEndElement(); + } + // save_folder(stream, item, type, set_ids_only); + } else { + int folder = root ? 0 : project_model.getItem(parent)->temp_id; + if (type == MEDIA_TYPE_FOOTAGE) { + Footage* f = m->to_footage(); + f->save_id = media_id; + stream.writeStartElement("footage"); + stream.writeAttribute("id", QString::number(media_id)); + stream.writeAttribute("folder", QString::number(folder)); + stream.writeAttribute("name", f->name); + stream.writeAttribute("url", proj_dir.relativeFilePath(f->url)); + stream.writeAttribute("duration", QString::number(f->length)); + stream.writeAttribute("using_inout", QString::number(f->using_inout)); + stream.writeAttribute("in", QString::number(f->in)); + stream.writeAttribute("out", QString::number(f->out)); + for (int j=0;jvideo_tracks.size();j++) { + FootageStream* ms = f->video_tracks.at(j); + stream.writeStartElement("video"); + stream.writeAttribute("id", QString::number(ms->file_index)); + stream.writeAttribute("width", QString::number(ms->video_width)); + stream.writeAttribute("height", QString::number(ms->video_height)); + stream.writeAttribute("framerate", QString::number(ms->video_frame_rate, 'f', 10)); + stream.writeAttribute("infinite", QString::number(ms->infinite_length)); + stream.writeEndElement(); + } + for (int j=0;jaudio_tracks.size();j++) { + FootageStream* ms = f->audio_tracks.at(j); + stream.writeStartElement("audio"); + stream.writeAttribute("id", QString::number(ms->file_index)); + stream.writeAttribute("channels", QString::number(ms->audio_channels)); + stream.writeAttribute("layout", QString::number(ms->audio_layout)); + stream.writeAttribute("frequency", QString::number(ms->audio_frequency)); + stream.writeEndElement(); + } + stream.writeEndElement(); + media_id++; + } else if (type == MEDIA_TYPE_SEQUENCE) { + Sequence* s = m->to_sequence(); + if (set_ids_only) { + s->save_id = sequence_id; + sequence_id++; + } else { + stream.writeStartElement("sequence"); + stream.writeAttribute("id", QString::number(s->save_id)); + stream.writeAttribute("folder", QString::number(folder)); + stream.writeAttribute("name", s->name); + stream.writeAttribute("width", QString::number(s->width)); + stream.writeAttribute("height", QString::number(s->height)); + stream.writeAttribute("framerate", QString::number(s->frame_rate, 'f', 10)); + stream.writeAttribute("afreq", QString::number(s->audio_frequency)); + stream.writeAttribute("alayout", QString::number(s->audio_layout)); + if (s == sequence) { + stream.writeAttribute("open", "1"); + } + stream.writeAttribute("workarea", QString::number(s->using_workarea)); + stream.writeAttribute("workareaIn", QString::number(s->workarea_in)); + stream.writeAttribute("workareaOut", QString::number(s->workarea_out)); + + for (int j=0;jtransitions.size();j++) { + Transition* t = s->transitions.at(j); + if (t != NULL) { + stream.writeStartElement("transition"); + stream.writeAttribute("id", QString::number(j)); + stream.writeAttribute("length", QString::number(t->get_true_length())); + t->save(stream); + stream.writeEndElement(); // transition + } + } + + for (int j=0;jclips.size();j++) { + Clip* c = s->clips.at(j); + if (c != NULL) { + stream.writeStartElement("clip"); // clip + stream.writeAttribute("id", QString::number(j)); + stream.writeAttribute("enabled", QString::number(c->enabled)); + stream.writeAttribute("name", c->name); + stream.writeAttribute("clipin", QString::number(c->clip_in)); + stream.writeAttribute("in", QString::number(c->timeline_in)); + stream.writeAttribute("out", QString::number(c->timeline_out)); + stream.writeAttribute("track", QString::number(c->track)); + stream.writeAttribute("opening", QString::number(c->opening_transition)); + stream.writeAttribute("closing", QString::number(c->closing_transition)); + + stream.writeAttribute("r", QString::number(c->color_r)); + stream.writeAttribute("g", QString::number(c->color_g)); + stream.writeAttribute("b", QString::number(c->color_b)); + + stream.writeAttribute("autoscale", QString::number(c->autoscale)); + stream.writeAttribute("speed", QString::number(c->speed, 'f', 10)); + stream.writeAttribute("maintainpitch", QString::number(c->maintain_audio_pitch)); + stream.writeAttribute("reverse", QString::number(c->reverse)); + + if (c->media != NULL) { + stream.writeAttribute("type", QString::number(c->media->get_type())); + switch (c->media->get_type()) { + case MEDIA_TYPE_FOOTAGE: + stream.writeAttribute("media", QString::number(c->media->to_footage()->save_id)); + stream.writeAttribute("stream", QString::number(c->media_stream)); + break; + case MEDIA_TYPE_SEQUENCE: + stream.writeAttribute("sequence", QString::number(c->media->to_sequence()->save_id)); + break; + } + } + + stream.writeStartElement("linked"); // linked + for (int k=0;klinked.size();k++) { + stream.writeStartElement("link"); // link + stream.writeAttribute("id", QString::number(c->linked.at(k))); + stream.writeEndElement(); // link + } + stream.writeEndElement(); // linked + + for (int k=0;keffects.size();k++) { + stream.writeStartElement("effect"); // effect + c->effects.at(k)->save(stream); + stream.writeEndElement(); // effect + } + + stream.writeEndElement(); // clip + } + } + for (int j=0;jmarkers.size();j++) { + stream.writeStartElement("marker"); + stream.writeAttribute("frame", QString::number(s->markers.at(j).frame)); + stream.writeAttribute("name", s->markers.at(j).name); + stream.writeEndElement(); + } + stream.writeEndElement(); + } + } + } + } + + if (m->get_type() == MEDIA_TYPE_FOLDER) { + save_folder(stream, type, set_ids_only, item); + } + } +} + +void Project::save_project(bool autorecovery) { + folder_id = 1; + media_id = 1; + sequence_id = 1; + + QFile file(autorecovery ? autorecovery_filename : project_url); + if (!file.open(QIODevice::WriteOnly/* | QIODevice::Text*/)) { + dout << "[ERROR] Could not open file"; + return; + } + + QXmlStreamWriter stream(&file); + stream.setAutoFormatting(true); + stream.writeStartDocument(); // doc + + stream.writeStartElement("project"); // project + + stream.writeTextElement("version", QString::number(SAVE_VERSION)); + + stream.writeTextElement("url", project_url); + proj_dir = QFileInfo(project_url).absoluteDir(); + + save_folder(stream, MEDIA_TYPE_FOLDER, true); + + stream.writeStartElement("folders"); // folders + save_folder(stream, MEDIA_TYPE_FOLDER, false); + stream.writeEndElement(); // folders + + stream.writeStartElement("media"); // media + save_folder(stream, MEDIA_TYPE_FOOTAGE, false); + stream.writeEndElement(); // media + + save_folder(stream, MEDIA_TYPE_SEQUENCE, true); + + stream.writeStartElement("sequences"); // sequences + save_folder(stream, MEDIA_TYPE_SEQUENCE, false); + stream.writeEndElement();// sequences + + stream.writeEndElement(); // project + + stream.writeEndDocument(); // doc + + file.close(); + + if (!autorecovery) { + add_recent_project(project_url); + mainWindow->setWindowModified(false); + } +} + +void Project::update_view_type() { + tree_view->setVisible(config.project_view_type == PROJECT_VIEW_TREE); + icon_view_container->setVisible(config.project_view_type == PROJECT_VIEW_ICON); + + switch (config.project_view_type) { + case PROJECT_VIEW_TREE: + sources_common->view = tree_view; + break; + case PROJECT_VIEW_ICON: + sources_common->view = icon_view; + break; + } +} + +void Project::set_icon_view() { + config.project_view_type = PROJECT_VIEW_ICON; + update_view_type(); +} + +void Project::set_tree_view() { + config.project_view_type = PROJECT_VIEW_TREE; + update_view_type(); +} + +void Project::save_recent_projects() { + // save to file + QFile f(recent_proj_file); + if (f.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) { + QTextStream out(&f); + for (int i=0;i 0) { + out << "\n"; + } + out << recent_projects.at(i); + } + f.close(); + } else { + dout << "[WARNING] Could not save recent projects"; + } +} + +void Project::clear_recent_projects() { + recent_projects.clear(); + save_recent_projects(); +} + +void Project::set_icon_view_size(int s) { + icon_view->setIconSize(QSize(s, s)); +} + +void Project::set_up_dir_enabled() { + directory_up->setEnabled(icon_view->rootIndex().isValid()); +} + +void Project::go_up_dir() { + icon_view->setRootIndex(icon_view->rootIndex().parent()); + set_up_dir_enabled(); +} + +void Project::add_recent_project(QString url) { + bool found = false; + for (int i=0;i MAXIMUM_RECENT_PROJECTS) { + recent_projects.removeLast(); + } + } + save_recent_projects(); +} + +void Project::list_all_sequences_worker(QVector* list, Media* parent) { + for (int i=0;iget_type()) { + case MEDIA_TYPE_SEQUENCE: + list->append(item); + break; + case MEDIA_TYPE_FOLDER: + list_all_sequences_worker(list, item); + break; + } + } +} + +QVector Project::list_all_project_sequences() { + QVector list; + list_all_sequences_worker(&list, NULL); + return list; +} + +#define THROBBER_LIMIT 20 +#define THROBBER_SIZE 50 + +MediaThrobber::MediaThrobber(Media *i) : pixmap(":/icons/throbber.png"), animation(0), item(i), animator(NULL) {} + +void MediaThrobber::start() { + // set up throbber + animation_update(); + animator = new QTimer(this); + animator->setInterval(20); + connect(animator, SIGNAL(timeout()), this, SLOT(animation_update())); + animator->start(); +} + +void MediaThrobber::animation_update() { + if (animation == THROBBER_LIMIT) { + animation = 0; + } + project_model.set_icon(item, QIcon(pixmap.copy(THROBBER_SIZE*animation, 0, THROBBER_SIZE, THROBBER_SIZE))); + animation++; +} + +void MediaThrobber::stop(int icon_type, bool replace) { + if (animator != NULL) { + animator->stop(); + delete animator; + } + + switch (icon_type) { + case ICON_TYPE_VIDEO: project_model.set_icon(item, QIcon(":/icons/videosource.png")); break; + case ICON_TYPE_AUDIO: project_model.set_icon(item, QIcon(":/icons/audiosource.png")); break; + case ICON_TYPE_IMAGE: project_model.set_icon(item, QIcon(":/icons/imagesource.png")); break; + case ICON_TYPE_ERROR: project_model.set_icon(item, QIcon(":/icons/error.png")); break; + } + + // refresh all clips + QVector sequences = panel_project->list_all_project_sequences(); + for (int i=0;ito_sequence(); + for (int j=0;jclips.size();j++) { + Clip* c = s->clips.at(j); + if (c != NULL) { + c->refresh(); + } + } + } + + // redraw clips + update_ui(replace); + + panel_project->tree_view->viewport()->update(); + item->throbber = NULL; + deleteLater(); +} diff --git a/panels/project.h b/panels/project.h index 563ae5ac0..71fdd0137 100644 --- a/panels/project.h +++ b/panels/project.h @@ -20,6 +20,9 @@ class QXmlStreamReader; class QFile; class QSortFilterProxyModel; class ComboAction; +class SourceIconView; +class QPushButton; +class SourcesCommon; #define LOAD_TYPE_VERSION 69 #define LOAD_TYPE_URL 70 @@ -40,10 +43,8 @@ Sequence* create_sequence_from_media(QVector &media_list); QString get_channel_layout_name(int channels, uint64_t layout); QString get_interlacing_name(int interlacing); -class Project : public QDockWidget -{ +class Project : public QDockWidget { Q_OBJECT - public: explicit Project(QWidget *parent = 0); ~Project(); @@ -54,7 +55,7 @@ public: void process_file_list(QStringList& files, bool recursive = false, Media* replace = NULL, Media *parent = NULL); void replace_media(Media* item, QString filename); Media *get_selected_folder(); - bool reveal_media(void* media, QModelIndex parent = QModelIndex()); + bool reveal_media(Media *media, QModelIndex parent = QModelIndex()); void add_recent_project(QString url); void new_project(); @@ -68,7 +69,10 @@ public: QVector list_all_project_sequences(); - SourceTable* source_table; + SourceTable* tree_view; + SourceIconView* icon_view; + SourcesCommon* sources_common; + QSortFilterProxyModel* sorter; QVector last_imported_media; @@ -93,8 +97,16 @@ private: void list_all_sequences_worker(QVector *list, Media* parent); QString get_file_name_from_path(const QString &path); QDir proj_dir; + QWidget* icon_view_container; + QPushButton* directory_up; private slots: + void update_view_type(); + void set_icon_view(); + void set_tree_view(); void clear_recent_projects(); + void set_icon_view_size(int); + void set_up_dir_enabled(); + void go_up_dir(); }; class MediaThrobber : public QObject { diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 351a47ba3..a5e0671e4 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -1,654 +1,653 @@ -#include "viewer.h" -#include "ui_viewer.h" - -#include "playback/audio.h" -#include "timeline.h" -#include "panels/project.h" -#include "project/sequence.h" -#include "project/clip.h" -#include "panels/panels.h" -#include "io/config.h" -#include "project/footage.h" -#include "project/media.h" -#include "project/undo.h" -#include "ui/audiomonitor.h" -#include "ui_timeline.h" -#include "playback/playback.h" -#include "ui/viewerwidget.h" -#include "debug.h" - -#define FRAMES_IN_ONE_MINUTE 1798 // 1800 - 2 -#define FRAMES_IN_TEN_MINUTES 17978 // (FRAMES_IN_ONE_MINUTE * 10) - 2 - -extern "C" { - #include - #include -} - -#include -#include -#include -#include -#include - -Viewer::Viewer(QWidget *parent) : - QDockWidget(parent), - playing(false), - just_played(false), - media(NULL), - seq(NULL), - ui(new Ui::Viewer), - created_sequence(false), - cue_recording_internal(false), - panel_name("Viewer: "), - minimum_zoom(1.0) -{ - ui->setupUi(this); - ui->headers->viewer = this; - ui->headers->snapping = false; - ui->headers->show_text(false); - ui->glViewerPane->viewer = this; - viewer_widget = ui->glViewerPane->child; - viewer_widget->viewer = this; - set_media(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())); - - recording_flasher.setInterval(500); - - connect(&playback_updater, SIGNAL(timeout()), this, SLOT(timer_update())); - connect(&recording_flasher, SIGNAL(timeout()), this, SLOT(recording_flasher_update())); - connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), ui->headers, SLOT(set_scroll(int))); - connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), viewer_widget, SLOT(set_waveform_scroll(int))); - connect(ui->horizontalScrollBar, SIGNAL(resize_move(double)), this, SLOT(resize_move(double))); - connect(ui->zoomComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(zoom_update(int))); - - update_playhead_timecode(0); - update_end_timecode(); -} - -Viewer::~Viewer() { - delete ui; -} - -bool Viewer::is_focused() { - return ui->headers->hasFocus() - || viewer_widget->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(); -} - -long timecode_to_frame(const QString& s, int view, double frame_rate) { - QList list = s.split(QRegExp("[:;]")); - - if (view == TIMECODE_FRAMES || (list.size() == 1 && view != TIMECODE_MILLISECONDS)) { - return s.toLong(); - } - - int frRound = qRound(frame_rate); - int hours, minutes, seconds, frames; - - if (view == TIMECODE_MILLISECONDS) { - long milliseconds = s.toLong(); - - hours = milliseconds/3600000; - milliseconds -= (hours*3600000); - minutes = milliseconds/60000; - milliseconds -= (minutes*60000); - seconds = milliseconds/1000; - milliseconds -= (seconds*1000); - frames = qRound64((milliseconds*0.001)*frame_rate); - - seconds = qRound64(seconds * frame_rate); - minutes = qRound64(minutes * frame_rate * 60); - hours = qRound64(hours * frame_rate * 3600); - } else { - hours = ((list.size() > 0) ? list.at(0).toInt() : 0) * frRound * 3600; - minutes = ((list.size() > 1) ? list.at(1).toInt() : 0) * frRound * 60; - seconds = ((list.size() > 2) ? list.at(2).toInt() : 0) * frRound; - frames = (list.size() > 3) ? list.at(3).toInt() : 0; - } - - int f = (frames + seconds + minutes + hours); - - if ((view == TIMECODE_DROP || view == TIMECODE_MILLISECONDS) && frame_rate_is_droppable(frame_rate)) { - // return drop - 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 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 - - d = f / framesPer10Minutes; - f -= dropFrames*9*d; - - m = f % framesPer10Minutes; - - if (m > dropFrames) { - f -= (dropFrames * ((m - dropFrames) / framesPerMinute)); - } - } - - // return non-drop - return f; -} - -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 || view == TIMECODE_MILLISECONDS) && 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 frqRound64ames 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; - } - if (view == TIMECODE_MILLISECONDS) { - return QString::number((hours*3600000)+(mins*60000)+(secs*1000)+qCeil(frames*1000/frame_rate)); - } - 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(); - reset_all_audio(); - audio_scrub = true; -} - -void Viewer::go_to_start() { - if (seq != NULL) { - if (seq->using_workarea && seq->playhead != seq->workarea_in) { - seek(seq->workarea_in); - } else { - seek(0); - } - } -} - -void Viewer::previous_frame() { - if (seq != NULL && seq->playhead > 0) seek(seq->playhead-1); -} - -void Viewer::next_frame() { - if (seq != NULL) seek(seq->playhead+1); -} - -void Viewer::go_to_end() { - if (seq != NULL) { - if (seq->using_workarea && seq->playhead != seq->workarea_out) { - seek(seq->workarea_out); - } else { - seek(seq->getEndFrame()); - } - } -} - -void Viewer::cue_recording(long start, long end, int track) { - recording_start = start; - recording_end = end; - recording_track = track; - panel_sequence_viewer->seek(recording_start); - cue_recording_internal = true; - recording_flasher.start(); -} - -void Viewer::uncue_recording() { - cue_recording_internal = false; - recording_flasher.stop(); - ui->pushButton_3->setStyleSheet(QString()); -} - -bool Viewer::is_recording_cued() { - return cue_recording_internal; -} - -void Viewer::toggle_play() { - if (playing) { - pause(); - } else { - play(); - } -} - -void Viewer::play() { - if (panel_sequence_viewer->playing) panel_sequence_viewer->pause(); - if (panel_footage_viewer->playing) panel_footage_viewer->pause(); - - if (seq != NULL) { - reset_all_audio(); - if (is_recording_cued() && !start_recording()) { - dout << "[ERROR] Failed to record audio"; - return; - } - playhead_start = seq->playhead; - playing = true; - just_played = true; - set_playpause_icon(false); - start_msecs = QDateTime::currentMSecsSinceEpoch(); - timer_update(); - } -} - -void Viewer::play_wake() { - if (just_played) { - start_msecs = QDateTime::currentMSecsSinceEpoch(); - playback_updater.start(); - if (audio_thread != NULL) audio_thread->notifyReceiver(); - just_played = false; - } -} - -void Viewer::pause() { - playing = false; - just_played = false; - set_playpause_icon(true); - playback_updater.stop(); - - if (is_recording_cued()) { - uncue_recording(); - - if (recording) { - stop_recording(); - - // import audio - QStringList file_list; - file_list.append(get_recorded_audio_filename()); - panel_project->process_file_list(file_list); - - // add it to the sequence - Clip* c = new Clip(seq); - Media* m = panel_project->last_imported_media.at(0); - Footage* f = m->to_footage(); - - f->ready_lock.lock(); - - c->media = m; // latest media - c->media_stream = 0; - c->timeline_in = recording_start; - c->timeline_out = f->get_length_in_frames(seq->frame_rate); - c->clip_in = 0; - c->track = recording_track; - c->color_r = 128; - c->color_g = 192; - c->color_b = 128; - c->name = m->get_name(); - - f->ready_lock.unlock(); - - QVector add_clips; - add_clips.append(c); - undo_stack.push(new AddClipCommand(seq, add_clips)); // add clip - } - } -} - -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(); - if (cached_end_frame != sequenceEndFrame) { - minimum_zoom = (sequenceEndFrame > 0) ? ((double) ui->headers->width() / (double) sequenceEndFrame) : 1; - ui->headers->update_zoom(qMax(ui->headers->get_zoom(), minimum_zoom)); - set_sb_max(); - viewer_widget->waveform_zoom = ui->headers->get_zoom(); - } else { - ui->headers->update(); - } - } -} - -void Viewer::update_parents() { - if (main_sequence) { - update_ui(false); - } else { - update_viewer(); - } -} - -void Viewer::resizeEvent(QResizeEvent *event) { - if (seq != NULL) { - set_sb_max(); - } -} - -void Viewer::update_viewer() { - update_header_zoom(); - viewer_widget->update(); - 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_zoom(bool in) { - if (seq != NULL) { - set_zoom_value(in ? ui->headers->get_zoom()*2 : qMax(minimum_zoom, ui->headers->get_zoom()*0.5)); - } -} - -void Viewer::set_zoom_value(double d) { - ui->headers->update_zoom(d); - if (viewer_widget->waveform) { - viewer_widget->waveform_zoom = d; - viewer_widget->update(); - } - if (seq != NULL) { - set_sb_max(); - if (!ui->horizontalScrollBar->is_resizing()) - center_scroll_to_playhead(ui->horizontalScrollBar, ui->headers->get_zoom(), seq->playhead); - } -} - -void Viewer::set_sb_max() { - ui->headers->set_scrollbar_max(ui->horizontalScrollBar, seq->getEndFrame(), ui->headers->width()); -} - -void Viewer::set_media(Media* m) { - main_sequence = false; - media = m; - clean_created_seq(); - if (media != NULL) { - switch (media->get_type()) { - case MEDIA_TYPE_FOOTAGE: - { - Footage* footage = media->to_footage(); - - seq = new Sequence(); - created_sequence = true; - seq->wrapper_sequence = true; - seq->name = footage->name; - - seq->using_workarea = footage->using_inout; - if (footage->using_inout) { - seq->workarea_in = footage->in; - seq->workarea_out = footage->out; - } - - seq->frame_rate = 30; - - if (footage->video_tracks.size() > 0) { - FootageStream* 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 && !video_stream->infinite_length) seq->frame_rate = video_stream->video_frame_rate; - - Clip* c = new Clip(seq); - c->media = media; - c->media_stream = video_stream->file_index; - c->timeline_in = 0; - c->timeline_out = footage->get_length_in_frames(seq->frame_rate); - if (c->timeline_out <= 0) c->timeline_out = 150; - c->track = -1; - c->clip_in = 0; - c->recalculateMaxLength(); - seq->clips.append(c); - } else { - seq->width = 1920; - seq->height = 1080; - } - - if (footage->audio_tracks.size() > 0) { - FootageStream* audio_stream = footage->audio_tracks.at(0); - seq->audio_frequency = audio_stream->audio_frequency; - - Clip* c = new Clip(seq); - c->media = media; - 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); - - if (footage->video_tracks.size() == 0) { - viewer_widget->waveform = true; - viewer_widget->waveform_clip = c; - viewer_widget->waveform_ms = audio_stream; - viewer_widget->update(); - } - } else { - seq->audio_frequency = 48000; - } - - seq->audio_layout = AV_CH_LAYOUT_STEREO; - } - break; - case MEDIA_TYPE_SEQUENCE: - seq = media->to_sequence(); - break; - } - } - set_sequence(false, seq); -} - -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() { - long previous_playhead = seq->playhead; - - seq->playhead = qRound(playhead_start + ((QDateTime::currentMSecsSinceEpoch()-start_msecs) * 0.001 * seq->frame_rate)); - update_parents(); - - long end_frame = (seq->using_workarea && previous_playhead < seq->workarea_out) ? seq->workarea_out : seq->getEndFrame(); - if ((!recording - && playing - && seq->playhead >= end_frame - && previous_playhead < end_frame) - || (recording && recording_start != recording_end && seq->playhead >= recording_end)) { - pause(); - } -} - -void Viewer::recording_flasher_update() { - if (ui->pushButton_3->styleSheet().isEmpty()) { - ui->pushButton_3->setStyleSheet("background: red;"); - } else { - ui->pushButton_3->setStyleSheet(QString()); - } -} - -void Viewer::zoom_update(int i) { - if (i == 0) { - ui->glViewerPane->fit = true; - } else { - ui->glViewerPane->fit = false; - QString pc = ui->zoomComboBox->itemText(i); - pc = pc.left(pc.length() - 1); - ui->glViewerPane->zoom = pc.toDouble()*0.01; - } - ui->glViewerPane->adjust(); -} - -void Viewer::resize_move(double d) { - dout << "RM"; - set_zoom_value(ui->headers->get_zoom()*d); -} - -void Viewer::clean_created_seq() { - viewer_widget->waveform = false; - - 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); - viewer_widget->setEnabled(!null_sequence); - viewer_widget->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); - - 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->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")); -} +#include "viewer.h" +#include "ui_viewer.h" + +#include "playback/audio.h" +#include "timeline.h" +#include "panels/project.h" +#include "project/sequence.h" +#include "project/clip.h" +#include "panels/panels.h" +#include "io/config.h" +#include "project/footage.h" +#include "project/media.h" +#include "project/undo.h" +#include "ui/audiomonitor.h" +#include "ui_timeline.h" +#include "playback/playback.h" +#include "ui/viewerwidget.h" +#include "debug.h" + +#define FRAMES_IN_ONE_MINUTE 1798 // 1800 - 2 +#define FRAMES_IN_TEN_MINUTES 17978 // (FRAMES_IN_ONE_MINUTE * 10) - 2 + +extern "C" { + #include + #include +} + +#include +#include +#include +#include +#include + +Viewer::Viewer(QWidget *parent) : + QDockWidget(parent), + playing(false), + just_played(false), + media(NULL), + seq(NULL), + ui(new Ui::Viewer), + created_sequence(false), + cue_recording_internal(false), + panel_name("Viewer: "), + minimum_zoom(1.0) +{ + ui->setupUi(this); + ui->headers->viewer = this; + ui->headers->snapping = false; + ui->headers->show_text(false); + ui->glViewerPane->viewer = this; + viewer_widget = ui->glViewerPane->child; + viewer_widget->viewer = this; + set_media(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())); + + recording_flasher.setInterval(500); + + connect(&playback_updater, SIGNAL(timeout()), this, SLOT(timer_update())); + connect(&recording_flasher, SIGNAL(timeout()), this, SLOT(recording_flasher_update())); + connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), ui->headers, SLOT(set_scroll(int))); + connect(ui->horizontalScrollBar, SIGNAL(valueChanged(int)), viewer_widget, SLOT(set_waveform_scroll(int))); + connect(ui->horizontalScrollBar, SIGNAL(resize_move(double)), this, SLOT(resize_move(double))); + connect(ui->zoomComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(zoom_update(int))); + + update_playhead_timecode(0); + update_end_timecode(); +} + +Viewer::~Viewer() { + delete ui; +} + +bool Viewer::is_focused() { + return ui->headers->hasFocus() + || viewer_widget->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(); +} + +long timecode_to_frame(const QString& s, int view, double frame_rate) { + QList list = s.split(QRegExp("[:;]")); + + if (view == TIMECODE_FRAMES || (list.size() == 1 && view != TIMECODE_MILLISECONDS)) { + return s.toLong(); + } + + int frRound = qRound(frame_rate); + int hours, minutes, seconds, frames; + + if (view == TIMECODE_MILLISECONDS) { + long milliseconds = s.toLong(); + + hours = milliseconds/3600000; + milliseconds -= (hours*3600000); + minutes = milliseconds/60000; + milliseconds -= (minutes*60000); + seconds = milliseconds/1000; + milliseconds -= (seconds*1000); + frames = qRound64((milliseconds*0.001)*frame_rate); + + seconds = qRound64(seconds * frame_rate); + minutes = qRound64(minutes * frame_rate * 60); + hours = qRound64(hours * frame_rate * 3600); + } else { + hours = ((list.size() > 0) ? list.at(0).toInt() : 0) * frRound * 3600; + minutes = ((list.size() > 1) ? list.at(1).toInt() : 0) * frRound * 60; + seconds = ((list.size() > 2) ? list.at(2).toInt() : 0) * frRound; + frames = (list.size() > 3) ? list.at(3).toInt() : 0; + } + + int f = (frames + seconds + minutes + hours); + + if ((view == TIMECODE_DROP || view == TIMECODE_MILLISECONDS) && frame_rate_is_droppable(frame_rate)) { + // return drop + 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 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 + + d = f / framesPer10Minutes; + f -= dropFrames*9*d; + + m = f % framesPer10Minutes; + + if (m > dropFrames) { + f -= (dropFrames * ((m - dropFrames) / framesPerMinute)); + } + } + + // return non-drop + return f; +} + +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 || view == TIMECODE_MILLISECONDS) && 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 frqRound64ames 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; + } + if (view == TIMECODE_MILLISECONDS) { + return QString::number((hours*3600000)+(mins*60000)+(secs*1000)+qCeil(frames*1000/frame_rate)); + } + 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(); + reset_all_audio(); + audio_scrub = true; +} + +void Viewer::go_to_start() { + if (seq != NULL) { + if (seq->using_workarea && seq->playhead != seq->workarea_in) { + seek(seq->workarea_in); + } else { + seek(0); + } + } +} + +void Viewer::previous_frame() { + if (seq != NULL && seq->playhead > 0) seek(seq->playhead-1); +} + +void Viewer::next_frame() { + if (seq != NULL) seek(seq->playhead+1); +} + +void Viewer::go_to_end() { + if (seq != NULL) { + if (seq->using_workarea && seq->playhead != seq->workarea_out) { + seek(seq->workarea_out); + } else { + seek(seq->getEndFrame()); + } + } +} + +void Viewer::cue_recording(long start, long end, int track) { + recording_start = start; + recording_end = end; + recording_track = track; + panel_sequence_viewer->seek(recording_start); + cue_recording_internal = true; + recording_flasher.start(); +} + +void Viewer::uncue_recording() { + cue_recording_internal = false; + recording_flasher.stop(); + ui->pushButton_3->setStyleSheet(QString()); +} + +bool Viewer::is_recording_cued() { + return cue_recording_internal; +} + +void Viewer::toggle_play() { + if (playing) { + pause(); + } else { + play(); + } +} + +void Viewer::play() { + if (panel_sequence_viewer->playing) panel_sequence_viewer->pause(); + if (panel_footage_viewer->playing) panel_footage_viewer->pause(); + + if (seq != NULL) { + reset_all_audio(); + if (is_recording_cued() && !start_recording()) { + dout << "[ERROR] Failed to record audio"; + return; + } + playhead_start = seq->playhead; + playing = true; + just_played = true; + set_playpause_icon(false); + start_msecs = QDateTime::currentMSecsSinceEpoch(); + timer_update(); + } +} + +void Viewer::play_wake() { + if (just_played) { + start_msecs = QDateTime::currentMSecsSinceEpoch(); + playback_updater.start(); + if (audio_thread != NULL) audio_thread->notifyReceiver(); + just_played = false; + } +} + +void Viewer::pause() { + playing = false; + just_played = false; + set_playpause_icon(true); + playback_updater.stop(); + + if (is_recording_cued()) { + uncue_recording(); + + if (recording) { + stop_recording(); + + // import audio + QStringList file_list; + file_list.append(get_recorded_audio_filename()); + panel_project->process_file_list(file_list); + + // add it to the sequence + Clip* c = new Clip(seq); + Media* m = panel_project->last_imported_media.at(0); + Footage* f = m->to_footage(); + + f->ready_lock.lock(); + + c->media = m; // latest media + c->media_stream = 0; + c->timeline_in = recording_start; + c->timeline_out = f->get_length_in_frames(seq->frame_rate); + c->clip_in = 0; + c->track = recording_track; + c->color_r = 128; + c->color_g = 192; + c->color_b = 128; + c->name = m->get_name(); + + f->ready_lock.unlock(); + + QVector add_clips; + add_clips.append(c); + undo_stack.push(new AddClipCommand(seq, add_clips)); // add clip + } + } +} + +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(); + if (cached_end_frame != sequenceEndFrame) { + minimum_zoom = (sequenceEndFrame > 0) ? ((double) ui->headers->width() / (double) sequenceEndFrame) : 1; + ui->headers->update_zoom(qMax(ui->headers->get_zoom(), minimum_zoom)); + set_sb_max(); + viewer_widget->waveform_zoom = ui->headers->get_zoom(); + } else { + ui->headers->update(); + } + } +} + +void Viewer::update_parents() { + if (main_sequence) { + update_ui(false); + } else { + update_viewer(); + } +} + +void Viewer::resizeEvent(QResizeEvent *event) { + if (seq != NULL) { + set_sb_max(); + } +} + +void Viewer::update_viewer() { + update_header_zoom(); + viewer_widget->update(); + 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_zoom(bool in) { + if (seq != NULL) { + set_zoom_value(in ? ui->headers->get_zoom()*2 : qMax(minimum_zoom, ui->headers->get_zoom()*0.5)); + } +} + +void Viewer::set_zoom_value(double d) { + ui->headers->update_zoom(d); + if (viewer_widget->waveform) { + viewer_widget->waveform_zoom = d; + viewer_widget->update(); + } + if (seq != NULL) { + set_sb_max(); + if (!ui->horizontalScrollBar->is_resizing()) + center_scroll_to_playhead(ui->horizontalScrollBar, ui->headers->get_zoom(), seq->playhead); + } +} + +void Viewer::set_sb_max() { + ui->headers->set_scrollbar_max(ui->horizontalScrollBar, seq->getEndFrame(), ui->headers->width()); +} + +void Viewer::set_media(Media* m) { + main_sequence = false; + media = m; + clean_created_seq(); + if (media != NULL) { + switch (media->get_type()) { + case MEDIA_TYPE_FOOTAGE: + { + Footage* footage = media->to_footage(); + + seq = new Sequence(); + created_sequence = true; + seq->wrapper_sequence = true; + seq->name = footage->name; + + seq->using_workarea = footage->using_inout; + if (footage->using_inout) { + seq->workarea_in = footage->in; + seq->workarea_out = footage->out; + } + + seq->frame_rate = 30; + + if (footage->video_tracks.size() > 0) { + FootageStream* 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 && !video_stream->infinite_length) seq->frame_rate = video_stream->video_frame_rate; + + Clip* c = new Clip(seq); + c->media = media; + c->media_stream = video_stream->file_index; + c->timeline_in = 0; + c->timeline_out = footage->get_length_in_frames(seq->frame_rate); + if (c->timeline_out <= 0) c->timeline_out = 150; + c->track = -1; + c->clip_in = 0; + c->recalculateMaxLength(); + seq->clips.append(c); + } else { + seq->width = 1920; + seq->height = 1080; + } + + if (footage->audio_tracks.size() > 0) { + FootageStream* audio_stream = footage->audio_tracks.at(0); + seq->audio_frequency = audio_stream->audio_frequency; + + Clip* c = new Clip(seq); + c->media = media; + 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); + + if (footage->video_tracks.size() == 0) { + viewer_widget->waveform = true; + viewer_widget->waveform_clip = c; + viewer_widget->waveform_ms = audio_stream; + viewer_widget->update(); + } + } else { + seq->audio_frequency = 48000; + } + + seq->audio_layout = AV_CH_LAYOUT_STEREO; + } + break; + case MEDIA_TYPE_SEQUENCE: + seq = media->to_sequence(); + break; + } + } + set_sequence(false, seq); +} + +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() { + long previous_playhead = seq->playhead; + + seq->playhead = qRound(playhead_start + ((QDateTime::currentMSecsSinceEpoch()-start_msecs) * 0.001 * seq->frame_rate)); + update_parents(); + + long end_frame = (seq->using_workarea && previous_playhead < seq->workarea_out) ? seq->workarea_out : seq->getEndFrame(); + if ((!recording + && playing + && seq->playhead >= end_frame + && previous_playhead < end_frame) + || (recording && recording_start != recording_end && seq->playhead >= recording_end)) { + pause(); + } +} + +void Viewer::recording_flasher_update() { + if (ui->pushButton_3->styleSheet().isEmpty()) { + ui->pushButton_3->setStyleSheet("background: red;"); + } else { + ui->pushButton_3->setStyleSheet(QString()); + } +} + +void Viewer::zoom_update(int i) { + if (i == 0) { + ui->glViewerPane->fit = true; + } else { + ui->glViewerPane->fit = false; + QString pc = ui->zoomComboBox->itemText(i); + pc = pc.left(pc.length() - 1); + ui->glViewerPane->zoom = pc.toDouble()*0.01; + } + ui->glViewerPane->adjust(); +} + +void Viewer::resize_move(double d) { + set_zoom_value(ui->headers->get_zoom()*d); +} + +void Viewer::clean_created_seq() { + viewer_widget->waveform = false; + + 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); + viewer_widget->setEnabled(!null_sequence); + viewer_widget->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); + + 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->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")); +} diff --git a/panels/viewer.h b/panels/viewer.h index c826aea80..71d316fa1 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -1,105 +1,105 @@ -#ifndef VIEWER_H -#define VIEWER_H - -#include -#include - -class Timeline; -class ViewerWidget; -class Media; -struct Sequence; - -namespace Ui { -class Viewer; -} - -bool frame_rate_is_droppable(float rate); -long timecode_to_frame(const QString& s, int view, double frame_rate); -QString frame_to_timecode(long f, int view, double frame_rate); - -class Viewer : public QDockWidget -{ - Q_OBJECT - -public: - explicit Viewer(QWidget *parent = 0); - ~Viewer(); - - bool is_focused(); - void set_main_sequence(); - void set_media(Media *m); - void compose(); - void set_playpause_icon(bool play); - void update_playhead_timecode(long p); - void update_end_timecode(); - void update_header_zoom(); - void update_viewer(); - void clear_inout_point(); - void set_in_point(); - void set_out_point(); - void set_zoom(bool in); - - // playback functions - void go_to_start(); - void previous_frame(); - void next_frame(); - void seek(long p); - void toggle_play(); - void play(); - void pause(); - void go_to_end(); - bool playing; - long playhead_start; - qint64 start_msecs; - QTimer playback_updater; - bool just_played; - - void cue_recording(long start, long end, int track); - void uncue_recording(); - bool is_recording_cued(); - long recording_start; - long recording_end; - int recording_track; - - void reset_all_audio(); - void update_parents(); - - ViewerWidget* viewer_widget; - - Media* media; - Sequence* seq; - - Ui::Viewer *ui; - - void resizeEvent(QResizeEvent *event); - -public slots: - void play_wake(); - -private slots: - void on_pushButton_clicked(); - void on_pushButton_5_clicked(); - void on_pushButton_2_clicked(); - void on_pushButton_4_clicked(); - void on_pushButton_3_clicked(); - void update_playhead(); - void timer_update(); - void recording_flasher_update(); - void zoom_update(int i); - void resize_move(double d); -private: - void clean_created_seq(); - void set_sequence(bool main, Sequence* s); - bool main_sequence; - bool created_sequence; - long cached_end_frame; - QString panel_name; - double minimum_zoom; - void set_zoom_value(double d); - void set_sb_max(); - - bool cue_recording_internal; - QTimer recording_flasher; -}; - -#endif // VIEWER_H +#ifndef VIEWER_H +#define VIEWER_H + +#include +#include + +class Timeline; +class ViewerWidget; +class Media; +struct Sequence; + +namespace Ui { +class Viewer; +} + +bool frame_rate_is_droppable(float rate); +long timecode_to_frame(const QString& s, int view, double frame_rate); +QString frame_to_timecode(long f, int view, double frame_rate); + +class Viewer : public QDockWidget +{ + Q_OBJECT + +public: + explicit Viewer(QWidget *parent = 0); + ~Viewer(); + + bool is_focused(); + void set_main_sequence(); + void set_media(Media *m); + void compose(); + void set_playpause_icon(bool play); + void update_playhead_timecode(long p); + void update_end_timecode(); + void update_header_zoom(); + void update_viewer(); + void clear_inout_point(); + void set_in_point(); + void set_out_point(); + void set_zoom(bool in); + + // playback functions + void go_to_start(); + void previous_frame(); + void next_frame(); + void seek(long p); + void toggle_play(); + void play(); + void pause(); + void go_to_end(); + bool playing; + long playhead_start; + qint64 start_msecs; + QTimer playback_updater; + bool just_played; + + void cue_recording(long start, long end, int track); + void uncue_recording(); + bool is_recording_cued(); + long recording_start; + long recording_end; + int recording_track; + + void reset_all_audio(); + void update_parents(); + + ViewerWidget* viewer_widget; + + Media* media; + Sequence* seq; + + Ui::Viewer *ui; + + void resizeEvent(QResizeEvent *event); + +public slots: + void play_wake(); + +private slots: + void on_pushButton_clicked(); + void on_pushButton_5_clicked(); + void on_pushButton_2_clicked(); + void on_pushButton_4_clicked(); + void on_pushButton_3_clicked(); + void update_playhead(); + void timer_update(); + void recording_flasher_update(); + void zoom_update(int i); + void resize_move(double d); +private: + void clean_created_seq(); + void set_sequence(bool main, Sequence* s); + bool main_sequence; + bool created_sequence; + long cached_end_frame; + QString panel_name; + double minimum_zoom; + void set_zoom_value(double d); + void set_sb_max(); + + bool cue_recording_internal; + QTimer recording_flasher; +}; + +#endif // VIEWER_H diff --git a/panels/viewer.ui b/panels/viewer.ui index 7d88f0742..2246fb0f6 100644 --- a/panels/viewer.ui +++ b/panels/viewer.ui @@ -70,99 +70,6 @@
- - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 260 - 20 - - - - - - - - - Fit - - - - - 10% - - - - - 25% - - - - - 50% - - - - - 75% - - - - - 100% - - - - - 150% - - - - - 200% - - - - - 400% - - - - - - - - Qt::Horizontal - - - - 259 - 20 - - - - - - - diff --git a/project/clip.cpp b/project/clip.cpp index df0927544..65fbdb50a 100644 --- a/project/clip.cpp +++ b/project/clip.cpp @@ -109,7 +109,12 @@ void Clip::refresh() { // validates media if it was replaced if (replaced && media != NULL && media->get_type() == MEDIA_TYPE_FOOTAGE) { Footage* m = media->to_footage(); - media_stream = (track < 0) ? m->video_tracks.at(0)->file_index : m->audio_tracks.at(0)->file_index; + + if (track < 0 && m->video_tracks.size() > 0) { + media_stream = m->video_tracks.at(0)->file_index; + } else if (track >= 0 && m->audio_tracks.size() > 0) { + media_stream = m->audio_tracks.at(0)->file_index; + } } replaced = false; diff --git a/project/footage.cpp b/project/footage.cpp index 6dc841eb7..b54f5a4a3 100644 --- a/project/footage.cpp +++ b/project/footage.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "io/previewgenerator.h" extern "C" { @@ -55,3 +56,16 @@ FootageStream* Footage::get_stream_from_file_index(bool video, int index) { } return NULL; } + +void FootageStream::make_square_thumb() { + // generate square version for QListView? + int square_size = qMax(video_preview.width(), video_preview.height()); + QPixmap pixmap(square_size, square_size); + pixmap.fill(Qt::transparent); + QPainter p(&pixmap); + int diff = (video_preview.width() - video_preview.height())>>1; + int sqx = (diff < 0) ? -diff : 0; + int sqy = (diff > 0) ? diff : 0; + p.drawImage(sqx, sqy, video_preview); + video_preview_square = QIcon(pixmap); +} diff --git a/project/footage.h b/project/footage.h index 55442e3ed..015253173 100644 --- a/project/footage.h +++ b/project/footage.h @@ -7,6 +7,7 @@ #include #include #include +#include #define VIDEO_PROGRESSIVE 0 #define VIDEO_TOP_FIELD_FIRST 1 @@ -32,7 +33,9 @@ struct FootageStream { // preview thumbnail/waveform bool preview_done; QImage video_preview; + QIcon video_preview_square; QVector audio_preview; + void make_square_thumb(); }; struct Footage { diff --git a/project/media.cpp b/project/media.cpp index bb01dc72e..cec9c7f8f 100644 --- a/project/media.cpp +++ b/project/media.cpp @@ -8,6 +8,8 @@ #include "panels/project.h" #include "projectmodel.h" +#include + #include "debug.h" extern "C" { @@ -253,6 +255,14 @@ QVariant Media::data(int column, int role) { switch (role) { case Qt::DecorationRole: if (column == 0) { + if (get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* f = to_footage(); + if (f->video_tracks.size() > 0 + && f->video_tracks.at(0)->preview_done) { + return f->video_tracks.at(0)->video_preview_square; + } + } + return icon; } break; diff --git a/project/sourcescommon.cpp b/project/sourcescommon.cpp new file mode 100644 index 000000000..dd1bd3da2 --- /dev/null +++ b/project/sourcescommon.cpp @@ -0,0 +1,280 @@ +#include "sourcescommon.h" + +#include "panels/panels.h" +#include "project/media.h" +#include "project/undo.h" +#include "panels/timeline.h" +#include "panels/project.h" +#include "project/footage.h" +#include "panels/viewer.h" +#include "io/config.h" +#include "mainwindow.h" + +#include +#include +#include +#include +#include +#include + +SourcesCommon::SourcesCommon(Project* parent) : + project_parent(parent), + editing_item(NULL) +{ + rename_timer.setInterval(1000); + connect(&rename_timer, SIGNAL(timeout()), this, SLOT(rename_interval())); +} + +void SourcesCommon::create_seq_from_selected() { + if (!selected_items.isEmpty()) { + QVector media_list; + for (int i=0;iitem_to_media(selected_items.at(i))); + } + + ComboAction* ca = new ComboAction(); + Sequence* s = create_sequence_from_media(media_list); + + // add clips to it + panel_timeline->create_ghosts_from_media(s, 0, media_list); + panel_timeline->add_clips_from_ghosts(ca, s); + + project_parent->new_sequence(ca, s, true, NULL); + undo_stack.push(ca); + } +} + +void SourcesCommon::show_context_menu(QWidget* parent, const QModelIndexList& items) { + QMenu menu(parent); + + selected_items = items; + + QAction* import_action = menu.addAction("Import..."); + QObject::connect(import_action, SIGNAL(triggered(bool)), project_parent, SLOT(import_dialog())); + + QAction* new_folder_action = menu.addAction("New Folder..."); + QObject::connect(new_folder_action, SIGNAL(triggered(bool)), mainWindow, SLOT(on_actionFolder_triggered())); + + if (items.size() > 0) { + Media* m = project_parent->item_to_media(items.at(0)); + + if (items.size() == 1) { + // replace footage + int type = m->get_type(); + if (type == MEDIA_TYPE_FOOTAGE) { + QAction* replace_action = menu.addAction("Replace/Relink Media"); + QObject::connect(replace_action, SIGNAL(triggered(bool)), project_parent, SLOT(replace_selected_file())); + +#if defined(Q_OS_WIN) + QAction* reveal_in_explorer = menu.addAction("Reveal in Explorer"); +#elif defined(Q_OS_MAC) + QAction* reveal_in_explorer = menu.addAction("Reveal in Finder"); +#else + QAction* reveal_in_explorer = menu.addAction("Reveal in File Manager"); +#endif + QObject::connect(reveal_in_explorer, SIGNAL(triggered(bool)), this, SLOT(reveal_in_browser())); + } + if (type != MEDIA_TYPE_FOLDER) { + QAction* replace_clip_media = menu.addAction("Replace Clips Using This Media"); + QObject::connect(replace_clip_media, SIGNAL(triggered(bool)), project_parent, SLOT(replace_clip_media())); + } + } + + // duplicate item + bool all_sequences = true; + bool all_footage = true; + for (int i=0;iget_type() != MEDIA_TYPE_SEQUENCE) { + all_sequences = false; + } + if (m->get_type() != MEDIA_TYPE_FOOTAGE) { + all_footage = false; + } + } + + // create sequence from + QAction* create_seq_from = menu.addAction("Create Sequence With This Media"); + QObject::connect(create_seq_from, SIGNAL(triggered(bool)), this, SLOT(create_seq_from_selected())); + + // ONLY sequences are selected + if (all_sequences) { + // ONLY sequences are selected + QAction* duplicate_action = menu.addAction("Duplicate"); + QObject::connect(duplicate_action, SIGNAL(triggered(bool)), project_parent, SLOT(duplicate_selected())); + } + + // ONLY footage is selected + if (all_footage) { + QAction* delete_footage_from_sequences = menu.addAction("Delete All Clips Using This Media"); + QObject::connect(delete_footage_from_sequences, SIGNAL(triggered(bool)), project_parent, SLOT(delete_clips_using_selected_media())); + } + + // delete media + QAction* delete_action = menu.addAction("Delete"); + QObject::connect(delete_action, SIGNAL(triggered(bool)), project_parent, SLOT(delete_selected_media())); + + if (items.size() == 1) { + QAction* properties_action = menu.addAction("Properties..."); + QObject::connect(properties_action, SIGNAL(triggered(bool)), project_parent, SLOT(open_properties())); + } + } + + menu.addSeparator(); + + QAction* tree_view_action = menu.addAction("Tree View"); + connect(tree_view_action, SIGNAL(triggered(bool)), project_parent, SLOT(set_tree_view())); + + QAction* icon_view_action = menu.addAction("Icon View"); + connect(icon_view_action, SIGNAL(triggered(bool)), project_parent, SLOT(set_icon_view())); + + menu.exec(QCursor::pos()); +} + +void SourcesCommon::mousePressEvent(QMouseEvent *) { + stop_rename_timer(); +} + +void SourcesCommon::item_click(Media *m, const QModelIndex& index) { + if (editing_item == m) { + rename_timer.start(); + } else { + editing_item = m; + editing_index = index; + } +} + +void SourcesCommon::mouseDoubleClickEvent(QMouseEvent *e, const QModelIndexList& selected_items) { + stop_rename_timer(); + if (selected_items.size() == 0) { + project_parent->import_dialog(); + } else if (selected_items.size() == 1) { + Media* item = project_parent->item_to_media(selected_items.at(0)); + switch (item->get_type()) { + case MEDIA_TYPE_FOOTAGE: + panel_footage_viewer->set_media(item); + panel_footage_viewer->setFocus(); + break; + case MEDIA_TYPE_SEQUENCE: + undo_stack.push(new ChangeSequenceAction(item->to_sequence())); + break; + } + } +} + +void SourcesCommon::dropEvent(QWidget* parent, QDropEvent *event, const QModelIndex& drop_item, const QModelIndexList& items) { + const QMimeData* mimeData = event->mimeData(); + Media* m = project_parent->item_to_media(drop_item); + if (mimeData->hasUrls()) { + // drag files in from outside + QList urls = mimeData->urls(); + if (!urls.isEmpty()) { + QStringList paths; + for (int i=0;iget_type() == MEDIA_TYPE_FOOTAGE + && !QFileInfo(paths.at(0)).isDir() + && config.drop_on_media_to_replace + && QMessageBox::question(parent, "Replace Media", "You dropped a file onto '" + m->get_name() + "'. Would you like to replace it with the dropped file?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { + replace = true; + project_parent->replace_media(m, paths.at(0)); + } + if (!replace) { + QModelIndex parent; + if (drop_item.isValid()) { + if (m->get_type() == MEDIA_TYPE_FOLDER) { + parent = drop_item; + } else { + parent = drop_item.parent(); + } + } + project_parent->process_file_list(paths, false, NULL, panel_project->item_to_media(parent)); + } + } + event->acceptProposedAction(); + } else { + event->ignore(); + + // dragging files within project + // if we dragged to the root OR dragged to a folder + if (!drop_item.isValid() || (drop_item.isValid() && m->get_type() == MEDIA_TYPE_FOLDER)) { + QVector move_items; + for (int i=0;iitem_to_media(item); + if (parent != drop_item && item != drop_item) { + bool ignore = false; + if (parent.isValid()) { + // if child belongs to a selected parent, assume the user is just moving the parent and ignore the child + QModelIndex par = parent; + while (par.isValid() && !ignore) { + for (int j=0;j 0) { + MediaMove* mm = new MediaMove(); + mm->to = m; + mm->items = move_items; + undo_stack.push(mm); + } + } + } +} + +void SourcesCommon::reveal_in_browser() { + Media* media = project_parent->item_to_media(selected_items.at(0)); + Footage* m = media->to_footage(); + +#if defined(Q_OS_WIN) + QStringList args; + args << "/select," << QDir::toNativeSeparators(m->url); + QProcess::startDetached("explorer", args); +#elif defined(Q_OS_MAC) + QStringList args; + args << "-e"; + args << "tell application \"Finder\""; + args << "-e"; + args << "activate"; + args << "-e"; + args << "select POSIX file \""+m->url+"\""; + args << "-e"; + args << "end tell"; + QProcess::startDetached("osascript", args); +#else + QDesktopServices::openUrl(QUrl::fromLocalFile(m->url.left(m->url.lastIndexOf('/')))); +#endif +} + +void SourcesCommon::stop_rename_timer() { + rename_timer.stop(); +} + +void SourcesCommon::rename_interval() { + stop_rename_timer(); + if (view->hasFocus() && editing_item != NULL) { + view->edit(editing_index); + } +} + +void SourcesCommon::item_renamed(Media* item) { + if (editing_item == item) { + MediaRename* mr = new MediaRename(item, "idk"); + undo_stack.push(mr); + editing_item = NULL; + } +} diff --git a/project/sourcescommon.h b/project/sourcescommon.h new file mode 100644 index 000000000..4522efdfa --- /dev/null +++ b/project/sourcescommon.h @@ -0,0 +1,39 @@ +#ifndef SOURCESCOMMON_H +#define SOURCESCOMMON_H + +#include +#include + +class Project; +class QMouseEvent; +class Media; +class QAbstractItemView; +class QDropEvent; + +class SourcesCommon : public QObject { + Q_OBJECT +public: + SourcesCommon(Project *parent); + QAbstractItemView* view; + void show_context_menu(QWidget* parent, const QModelIndexList &items); + + void mousePressEvent(QMouseEvent* e); + void mouseDoubleClickEvent(QMouseEvent* e, const QModelIndexList& selected_items); + void dropEvent(QWidget *parent, QDropEvent* e, const QModelIndex& drop_item, const QModelIndexList &items); + + void item_click(Media* m, const QModelIndex &index); +private slots: + void create_seq_from_selected(); + void reveal_in_browser(); + void rename_interval(); + void item_renamed(Media *item); +private: + Media* editing_item; + QModelIndex editing_index; + QModelIndexList selected_items; + Project* project_parent; + void stop_rename_timer(); + QTimer rename_timer; +}; + +#endif // SOURCESCOMMON_H diff --git a/project/undo.cpp b/project/undo.cpp index 17fe258d7..8e9132d82 100644 --- a/project/undo.cpp +++ b/project/undo.cpp @@ -592,6 +592,7 @@ void ReplaceMediaCommand::replace(QString& filename) { // replace media QStringList files; files.append(filename); + item->to_footage()->ready_lock.lock(); panel_project->process_file_list(files, false, item, NULL); } @@ -694,7 +695,7 @@ void EffectDeleteCommand::redo() { mainWindow->setWindowModified(true); } -MediaMove::MediaMove(SourceTable *s) : table(s), old_project_changed(mainWindow->isWindowModified()) {} +MediaMove::MediaMove() : old_project_changed(mainWindow->isWindowModified()) {} void MediaMove::undo() { for (int i=0;i items; Media* to; void undo(); void redo(); private: QVector froms; - SourceTable* table; bool old_project_changed; }; diff --git a/ui/otreeview.cpp b/ui/otreeview.cpp new file mode 100644 index 000000000..9ea03a828 --- /dev/null +++ b/ui/otreeview.cpp @@ -0,0 +1,9 @@ +#include "otreeview.h" + +OTreeView::OTreeView(QWidget *parent) : QTreeView(parent) { + setSortingEnabled(true); + sortByColumn(0, Qt::AscendingOrder); + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); + connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(item_click(const QModelIndex&))); +} diff --git a/ui/otreeview.h b/ui/otreeview.h new file mode 100644 index 000000000..810f8b5ec --- /dev/null +++ b/ui/otreeview.h @@ -0,0 +1,16 @@ +#ifndef OTREEVIEW_H +#define OTREEVIEW_H + +#include + +#include "ui/sourcetable.h" + +class OTreeView : public QTreeView { + Q_OBJECT +public: + OTreeView(QWidget* parent = 0); +private: + +}; + +#endif // OTREEVIEW_H diff --git a/ui/sourceiconview.cpp b/ui/sourceiconview.cpp new file mode 100644 index 000000000..9617adc24 --- /dev/null +++ b/ui/sourceiconview.cpp @@ -0,0 +1,67 @@ +#include "sourceiconview.h" + +#include + +#include "panels/project.h" +#include "project/media.h" +#include "project/sourcescommon.h" +#include "debug.h" + +SourceIconView::SourceIconView(QWidget *parent) : QListView(parent) { + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(item_click(const QModelIndex&))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); +} + +void SourceIconView::show_context_menu() { + project_parent->sources_common->show_context_menu(this, selectedIndexes()); +} + +void SourceIconView::item_click(const QModelIndex& index) { + if (selectionModel()->selectedRows().size() == 1 && index.column() == 0) { + project_parent->sources_common->item_click(project_parent->item_to_media(index), index); + } +} + +void SourceIconView::mousePressEvent(QMouseEvent* event) { + project_parent->sources_common->mousePressEvent(event); + if (!indexAt(event->pos()).isValid()) selectionModel()->clear(); + QListView::mousePressEvent(event); +} + +void SourceIconView::dragEnterEvent(QDragEnterEvent *event) { + if (event->mimeData()->hasUrls()) { + event->acceptProposedAction(); + } else { + QListView::dragEnterEvent(event); + } +} + +void SourceIconView::dragMoveEvent(QDragMoveEvent *event) { + if (event->mimeData()->hasUrls()) { + event->acceptProposedAction(); + } else { + QListView::dragMoveEvent(event); + } +} + +void SourceIconView::dropEvent(QDropEvent* event) { + QModelIndex drop_item = indexAt(event->pos()); + if (!drop_item.isValid()) drop_item = rootIndex(); + project_parent->sources_common->dropEvent(this, event, drop_item, selectedIndexes()); +} + +void SourceIconView::mouseDoubleClickEvent(QMouseEvent *event) { + bool default_behavior = true; + if (selectedIndexes().size() == 1) { + Media* m = project_parent->item_to_media(selectedIndexes().at(0)); + if (m->get_type() == MEDIA_TYPE_FOLDER) { + default_behavior = false; + setRootIndex(selectedIndexes().at(0)); + emit changed_root(); + } + } + if (default_behavior) { + project_parent->sources_common->mouseDoubleClickEvent(event, selectedIndexes()); + } +} diff --git a/ui/sourceiconview.h b/ui/sourceiconview.h new file mode 100644 index 000000000..9320a1628 --- /dev/null +++ b/ui/sourceiconview.h @@ -0,0 +1,26 @@ +#ifndef SOURCEICONVIEW_H +#define SOURCEICONVIEW_H + +#include + +class Project; + +class SourceIconView : public QListView { + Q_OBJECT +public: + SourceIconView(QWidget* parent = 0); + Project* project_parent; + + void mousePressEvent(QMouseEvent* event); + void mouseDoubleClickEvent(QMouseEvent *event); + void dragEnterEvent(QDragEnterEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dropEvent(QDropEvent* event); +signals: + void changed_root(); +private slots: + void show_context_menu(); + void item_click(const QModelIndex& index); +}; + +#endif // SOURCEICONVIEW_H diff --git a/ui/sourcetable.cpp b/ui/sourcetable.cpp index aa835f55d..75586e8b4 100644 --- a/ui/sourcetable.cpp +++ b/ui/sourcetable.cpp @@ -11,6 +11,7 @@ #include "mainwindow.h" #include "io/config.h" #include "project/media.h" +#include "project/sourcescommon.h" #include "debug.h" #include @@ -25,193 +26,30 @@ #include SourceTable::SourceTable(QWidget* parent) : QTreeView(parent) { - editing_item = NULL; setSortingEnabled(true); sortByColumn(0, Qt::AscendingOrder); - rename_timer.setInterval(1000); setContextMenuPolicy(Qt::CustomContextMenu); - connect(&rename_timer, SIGNAL(timeout()), this, SLOT(rename_interval())); connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(item_click(const QModelIndex&))); connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); } void SourceTable::show_context_menu() { - QMenu menu(this); - - QAction* import_action = menu.addAction("Import..."); - connect(import_action, SIGNAL(triggered(bool)), project_parent, SLOT(import_dialog())); - - QAction* new_folder_action = menu.addAction("New Folder..."); - connect(new_folder_action, SIGNAL(triggered(bool)), mainWindow, SLOT(on_actionFolder_triggered())); - - QModelIndexList selected_items = selectionModel()->selectedRows(); - - if (selected_items.size() > 0) { - Media* m = project_parent->item_to_media(selected_items.at(0)); - - if (selected_items.size() == 1) { - // replace footage - int type = m->get_type(); - if (type == MEDIA_TYPE_FOOTAGE) { - QAction* replace_action = menu.addAction("Replace/Relink Media"); - connect(replace_action, SIGNAL(triggered(bool)), project_parent, SLOT(replace_selected_file())); - -#if defined(Q_OS_WIN) - QAction* reveal_in_explorer = menu.addAction("Reveal in Explorer"); -#elif defined(Q_OS_MAC) - QAction* reveal_in_explorer = menu.addAction("Reveal in Finder"); -#else - QAction* reveal_in_explorer = menu.addAction("Reveal in File Manager"); -#endif - connect(reveal_in_explorer, SIGNAL(triggered(bool)), this, SLOT(reveal_in_browser())); - } - if (type != MEDIA_TYPE_FOLDER) { - QAction* replace_clip_media = menu.addAction("Replace Clips Using This Media"); - connect(replace_clip_media, SIGNAL(triggered(bool)), project_parent, SLOT(replace_clip_media())); - } - } - - // duplicate item - bool all_sequences = true; - bool all_footage = true; - for (int i=0;iget_type() != MEDIA_TYPE_SEQUENCE) { - all_sequences = false; - } - if (m->get_type() != MEDIA_TYPE_FOOTAGE) { - all_footage = false; - } - } - - // create sequence from - QAction* create_seq_from = menu.addAction("Create Sequence With This Media"); - connect(create_seq_from, SIGNAL(triggered(bool)), this, SLOT(create_seq_from_selected())); - - // ONLY sequences are selected - if (all_sequences) { - // ONLY sequences are selected - QAction* duplicate_action = menu.addAction("Duplicate"); - connect(duplicate_action, SIGNAL(triggered(bool)), project_parent, SLOT(duplicate_selected())); - } - - // ONLY footage is selected - if (all_footage) { - QAction* delete_footage_from_sequences = menu.addAction("Delete All Clips Using This Media"); - connect(delete_footage_from_sequences, SIGNAL(triggered(bool)), project_parent, SLOT(delete_clips_using_selected_media())); - } - - // delete media - QAction* delete_action = menu.addAction("Delete"); - connect(delete_action, SIGNAL(triggered(bool)), project_parent, SLOT(delete_selected_media())); - - if (selected_items.size() == 1) { - QAction* properties_action = menu.addAction("Properties..."); - connect(properties_action, SIGNAL(triggered(bool)), project_parent, SLOT(open_properties())); - } - } - - menu.exec(QCursor::pos()); + project_parent->sources_common->show_context_menu(this, selectionModel()->selectedRows()); } -void SourceTable::create_seq_from_selected() { - QModelIndexList selected_items = selectionModel()->selectedRows(); - - if (!selected_items.isEmpty()) { - QVector media_list; - for (int i=0;iitem_to_media(selected_items.at(i))); - } - - ComboAction* ca = new ComboAction(); - Sequence* s = create_sequence_from_media(media_list); - - // add clips to it - panel_timeline->create_ghosts_from_media(s, 0, media_list); - panel_timeline->add_clips_from_ghosts(ca, s); - - project_parent->new_sequence(ca, s, true, NULL); - undo_stack.push(ca); - } -} - -void SourceTable::reveal_in_browser() { - QModelIndexList selected_items = selectionModel()->selectedRows(); - Media* media = project_parent->item_to_media(selected_items.at(0)); - Footage* m = media->to_footage(); - -#if defined(Q_OS_WIN) - QStringList args; - args << "/select," << QDir::toNativeSeparators(m->url); - QProcess::startDetached("explorer", args); -#elif defined(Q_OS_MAC) - QStringList args; - args << "-e"; - args << "tell application \"Finder\""; - args << "-e"; - args << "activate"; - args << "-e"; - args << "select POSIX file \""+m->url+"\""; - args << "-e"; - args << "end tell"; - QProcess::startDetached("osascript", args); -#else - QDesktopServices::openUrl(QUrl::fromLocalFile(m->url.left(m->url.lastIndexOf('/')))); -#endif -} - -void SourceTable::item_renamed(Media* item) { - if (editing_item == item) { - MediaRename* mr = new MediaRename(item, "idk"); - undo_stack.push(mr); - editing_item = NULL; - } -} - -void SourceTable::stop_rename_timer() { - rename_timer.stop(); -} - -void SourceTable::rename_interval() { - stop_rename_timer(); - if (hasFocus() && editing_item != NULL) { - edit(editing_index); - //editItem(editing_item, 0); - } -} void SourceTable::item_click(const QModelIndex& index) { if (selectionModel()->selectedRows().size() == 1 && index.column() == 0) { - Media* m = project_parent->item_to_media(index); - if (editing_item == m) { - rename_timer.start(); - } else { - editing_item = m; - editing_index = index; - } + project_parent->sources_common->item_click(project_parent->item_to_media(index), index); } } void SourceTable::mousePressEvent(QMouseEvent* event) { - stop_rename_timer(); + project_parent->sources_common->mousePressEvent(event); QTreeView::mousePressEvent(event); } -void SourceTable::mouseDoubleClickEvent(QMouseEvent* e) { - stop_rename_timer(); - QModelIndexList selected_items = selectionModel()->selectedRows(); - if (selected_items.size() == 0) { - project_parent->import_dialog(); - } else if (selected_items.size() == 1) { - Media* item = project_parent->item_to_media(selected_items.at(0)); - switch (item->get_type()) { - case MEDIA_TYPE_FOOTAGE: - panel_footage_viewer->set_media(item); - panel_footage_viewer->setFocus(); - break; - case MEDIA_TYPE_SEQUENCE: - undo_stack.push(new ChangeSequenceAction(item->to_sequence())); - break; - } - } +void SourceTable::mouseDoubleClickEvent(QMouseEvent* event) { + project_parent->sources_common->mouseDoubleClickEvent(event, selectionModel()->selectedRows()); } void SourceTable::dragEnterEvent(QDragEnterEvent *event) { @@ -231,79 +69,5 @@ void SourceTable::dragMoveEvent(QDragMoveEvent *event) { } void SourceTable::dropEvent(QDropEvent* event) { - const QMimeData* mimeData = event->mimeData(); - const QModelIndex& drop_item = indexAt(event->pos()); - Media* m = project_parent->item_to_media(drop_item); - if (mimeData->hasUrls()) { - // drag files in from outside - QList urls = mimeData->urls(); - if (!urls.isEmpty()) { - QStringList paths; - for (int i=0;iget_type() == MEDIA_TYPE_FOOTAGE - && !QFileInfo(paths.at(0)).isDir() - && config.drop_on_media_to_replace - && QMessageBox::question(this, "Replace Media", "You dropped a file onto '" + m->get_name() + "'. Would you like to replace it with the dropped file?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { - replace = true; - project_parent->replace_media(m, paths.at(0)); - } - if (!replace) { - QModelIndex parent; - if (drop_item.isValid()) { - if (m->get_type() == MEDIA_TYPE_FOLDER) { - parent = drop_item; - } else { - parent = drop_item.parent(); - } - } - if (parent.isValid()) setExpanded(parent, true); - project_parent->process_file_list(paths, false, NULL, panel_project->item_to_media(parent)); - } - } - event->acceptProposedAction(); - } else { - event->ignore(); - - // dragging files within project - // if we dragged to the root OR dragged to a folder - if (!drop_item.isValid() || (drop_item.isValid() && m->get_type() == MEDIA_TYPE_FOLDER)) { - QVector move_items; - QModelIndexList selected_items = selectionModel()->selectedRows(); - for (int i=0;iitem_to_media(item); - if (parent != drop_item && item != drop_item) { - bool ignore = false; - if (parent.isValid()) { - // if child belongs to a selected parent, assume the user is just moving the parent and ignore the child - QModelIndex par = parent; - while (par.isValid() && !ignore) { - for (int j=0;j 0) { - MediaMove* mm = new MediaMove(this); - mm->to = m; - mm->items = move_items; - undo_stack.push(mm); - } - } - } + project_parent->sources_common->dropEvent(this, event, indexAt(event->pos()), selectionModel()->selectedRows()); } diff --git a/ui/sourcetable.h b/ui/sourcetable.h index c2a2637a7..aa19a5449 100644 --- a/ui/sourcetable.h +++ b/ui/sourcetable.h @@ -12,26 +12,17 @@ class SourceTable : public QTreeView { Q_OBJECT public: - SourceTable(QWidget* parent = 0); + SourceTable(QWidget* parent = 0); Project* project_parent; protected: void mousePressEvent(QMouseEvent*); - void mouseDoubleClickEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *); void dragEnterEvent(QDragEnterEvent *event); void dragMoveEvent(QDragMoveEvent *event); void dropEvent(QDropEvent *event); -private: - QTimer rename_timer; - Media* editing_item; - QModelIndex editing_index; private slots: - void rename_interval(); void item_click(const QModelIndex& index); - void stop_rename_timer(); - void item_renamed(Media *item); - void show_context_menu(); - void create_seq_from_selected(); - void reveal_in_browser(); + void show_context_menu(); }; #endif // SOURCETABLE_H diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index ba4d42fcb..0506995af 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -10,6 +10,7 @@ #include "panels/timeline.h" #include "project/footage.h" #include "ui/sourcetable.h" +#include "ui/sourceiconview.h" #include "panels/effectcontrols.h" #include "panels/viewer.h" #include "project/undo.h" @@ -265,8 +266,10 @@ void TimelineWidget::dragEnterEvent(QDragEnterEvent *event) { QVector media_list; panel_timeline->importing_files = false; - if (event->source() == panel_project->source_table) { - QModelIndexList items = panel_project->source_table->selectionModel()->selectedRows(); + if (event->source() == panel_project->tree_view || event->source() == panel_project->icon_view) { + QModelIndexList items = (event->source() == panel_project->tree_view) + ? panel_project->tree_view->selectionModel()->selectedRows() + : panel_project->icon_view->selectionModel()->selectedIndexes(); media_list.resize(items.size()); for (int i=0;iitem_to_media(items.at(i)); diff --git a/ui/timelinewidget.h b/ui/timelinewidget.h index 2ba01cab4..20e4bfea9 100644 --- a/ui/timelinewidget.h +++ b/ui/timelinewidget.h @@ -19,6 +19,7 @@ class TimelineAction; class QScrollBar; class SetSelectionsCommand; class QPainter; +class Media; bool same_sign(int a, int b); void draw_waveform(Clip* clip, FootageStream* ms, long media_length, QPainter* p, const QRect& clip_rect, int waveform_start, int waveform_limit, double zoom); @@ -65,7 +66,7 @@ private: // used for "right click ripple" long rc_ripple_min; long rc_ripple_max; - void* rc_reveal_media; + Media* rc_reveal_media; QTimer tooltip_timer; int tooltip_clip; diff --git a/ui/viewercontainer.cpp b/ui/viewercontainer.cpp index be24c84bf..5c109b077 100644 --- a/ui/viewercontainer.cpp +++ b/ui/viewercontainer.cpp @@ -75,6 +75,8 @@ void ViewerContainer::adjust() { child->move(widget_x, widget_y); child->resize(widget_width, widget_height); + + zoom = double(widget_width) / double(viewer->seq->width); } else { int zoomed_width = double(viewer->seq->width)*zoom; int zoomed_height = double(viewer->seq->height)*zoom; diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 0c80622cd..05156f77f 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -35,6 +35,7 @@ #include #include #include +#include extern "C" { #include @@ -91,6 +92,22 @@ void ViewerWidget::show_context_menu() { QAction* show_fullscreen_action = menu.addAction("Show Fullscreen"); connect(show_fullscreen_action, SIGNAL(triggered()), this, SLOT(show_fullscreen())); + QMenu zoom_menu("Zoom"); + QAction* fit_zoom = zoom_menu.addAction("Fit"); + connect(fit_zoom, SIGNAL(triggered(bool)), this, SLOT(set_fit_zoom())); + zoom_menu.addAction("10%")->setData(0.1); + zoom_menu.addAction("25%")->setData(0.25); + zoom_menu.addAction("50%")->setData(0.5); + zoom_menu.addAction("75%")->setData(0.75); + zoom_menu.addAction("100%")->setData(1.0); + zoom_menu.addAction("150%")->setData(1.5); + zoom_menu.addAction("200%")->setData(2.0); + zoom_menu.addAction("400%")->setData(4.0); + QAction* custom_zoom = zoom_menu.addAction("Custom"); + connect(custom_zoom, SIGNAL(triggered(bool)), this, SLOT(set_custom_zoom())); + connect(&zoom_menu, SIGNAL(triggered(QAction*)), this, SLOT(set_menu_zoom(QAction*))); + menu.addMenu(&zoom_menu); + menu.exec(QCursor::pos()); } @@ -130,6 +147,30 @@ void ViewerWidget::show_fullscreen() { showFullScreen(); } +void ViewerWidget::set_fit_zoom() { + container->fit = true; + container->adjust(); +} + +void ViewerWidget::set_custom_zoom() { + bool ok; + double d = QInputDialog::getDouble(this, "Viewer Zoom", "Set Custom Zoom Value:", container->zoom*100, 0, 2147483647, 2, &ok); + if (ok) { + container->fit = false; + container->zoom = d*0.01; + container->adjust(); + } +} + +void ViewerWidget::set_menu_zoom(QAction* action) { + const QVariant& data = action->data(); + if (!data.isNull()) { + container->fit = false; + container->zoom = data.toDouble(); + container->adjust(); + } +} + void ViewerWidget::retry() { update(); } @@ -640,12 +681,8 @@ GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) int widget_width = width(); int widget_height = height(); - QString qt_scale_factor = QString(qgetenv("QT_SCALE_FACTOR")); - if (!qt_scale_factor.isEmpty()) { - double scale = qt_scale_factor.toDouble(); - widget_width *= scale; - widget_height *= scale; - } + widget_width *= QApplication::desktop()->devicePixelRatio(); + widget_height *= QApplication::desktop()->devicePixelRatio(); glViewport(0, 0, widget_width, widget_height); } diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 8d9899151..fa51aa383 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -70,6 +70,10 @@ private slots: void show_context_menu(); void save_frame(); void show_fullscreen(); + + void set_fit_zoom(); + void set_custom_zoom(); + void set_menu_zoom(QAction *action); }; #endif // VIEWERWIDGET_H